Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
599339a270 | |||
b05b5de039 | |||
3566c9ba3e | |||
d88469e711 | |||
77e472e016 | |||
4637a8c579 |
43
.github/workflows/python-package.yml
vendored
Normal file
43
.github/workflows/python-package.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||
|
||||
name: Python package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y unixodbc
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8 pytest
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
pip install -e .
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest .
|
15
Changelog.md
Normal file
15
Changelog.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 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
|
||||
- kleinere Verbesserungen an Doku
|
||||
|
||||
## 04.05.2023 v1.0.0
|
||||
erste veröffentlichte Version
|
30
README.md
30
README.md
@ -39,16 +39,30 @@ aus, dass im Laufe der Zeit weitere Features hinzukommen.
|
||||
Aufrufe von SOAP-Methoden. Unsachgemäße Nutzung kann Ihre Daten zerstören. Benutzen Sie
|
||||
`PyAPplus64` daher bitte vorsichtig.
|
||||
|
||||
## Lizenz
|
||||
## Installation
|
||||
|
||||
PyAPplus64 wurde auf PyPi veröffentlicht. Es lässt sich daher einfach mittel `pip` installieren
|
||||
|
||||
````
|
||||
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
|
||||
````
|
||||
|
||||
`PyAPplus64` wurde unter MIT license veröffentlicht.
|
||||
|
||||
## Links
|
||||
|
||||
- Homepage https://www.thomas-tuerk.de/de/pyapplus64
|
||||
- Doku
|
||||
+ PDF https://www.thomas-tuerk.de/assets/PyAPplus64/pyapplus64.pdf
|
||||
+ HTML https://www.thomas-tuerk.de/assets/PyAPplus64/html/index.html
|
||||
- GIT-Repository https://git.thomas-tuerk.de/thtuerk/PyAPplus64
|
||||
- PyPI https://pypi.org/project/PyAPplus64/
|
||||
- [PyPi](https://pypi.org/project/PyAPplus64/)
|
||||
- Doku [PDF](https://www.thomas-tuerk.de/assets/PyAPplus64/pyapplus64.pdf), [HTML](https://www.thomas-tuerk.de/assets/PyAPplus64/html/index.html)
|
||||
- [GIT-Repository](https://git.thomas-tuerk.de/thtuerk/PyAPplus64)
|
||||
- [GitHub](https://github.com/thtuerk/PyAPplus64)
|
||||
|
||||
## Lizenz / Mitarbeit
|
||||
|
||||
Ich habe PyAPplus64 unter MIT License veröffentlicht. Diese Lizenz gibt Ihnen weitreichende Rechte für die Nutzung von PyAPplus64, auch im kommerziellen Kontext. Ich bitte aber dringend darum, Ihre Änderungen, Erweiterungen und Fehlerkorrekturen auch anderen zur Verfügung zu stellen. Dafür können Sie die üblichen Methoden auf Github nutzen oder mir ([Thomas Türk](mailto:kontakt@thomas-tuerk.de)) eine eMail mit den Änderungen schicken.
|
||||
|
||||
|
@ -13,6 +13,13 @@ 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
|
||||
------
|
||||
|
||||
@ -38,3 +45,4 @@ Pandas / SqlAlchemy / xlsxwriter
|
||||
--------------------------------
|
||||
Sollen Excel-Dateien mit Pandas erzeugt, werden, so muss Pandas, SqlAlchemy und xlsxwriter installiert sein
|
||||
(`python -m pip install pandas sqlalchemy xlsxwriter`).
|
||||
|
||||
|
@ -17,6 +17,7 @@ ausgegeben und das Feld `DOCUMENTS` gelöscht. Das Löschen erfolgt dabei über
|
||||
|
||||
.. literalinclude:: ../../examples/check_dokumente.py
|
||||
:language: python
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
Man kann alle Python Bibliotheken nutzen. Als Erweiterung wäre es in obigem Script
|
||||
@ -41,6 +42,7 @@ welche Materialen wie oft für Artikel benutzt werden:
|
||||
|
||||
.. literalinclude:: ../../examples/adhoc_report.py
|
||||
:language: python
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
Dieses kurze Script nutzt Standard-Pandas Methoden zur Erzeugung der Excel-Datei. Allerdings
|
||||
@ -56,7 +58,7 @@ der für die Umgebung korrekte Mandant automatisch verwendet wird.
|
||||
Anbindung eigener Tools
|
||||
-----------------------
|
||||
|
||||
Ursprünglich wurde `PyAPplus64` für die Anbindung einer APplus-Anpassung geschrieben. Dieses ist
|
||||
Ursprünglich wurde `PyAPplus64` für die Anbindung einer APplus-Anpassung geschrieben. Diese Anpassung ist
|
||||
als Windows-Service auf einem eigenen Rechner installiert und überwacht dort ein bestimmtes Verzeichnis.
|
||||
Bei Änderungen an Dateien in diesem Verzeichnis (Hinzufügen, Ändern, Löschen) werden die Dateien verarbeitet
|
||||
und die Ergebnisse an APplus gemeldet. Dafür werden DB-Operationen aber auch SOAP-Calls benutzt.
|
||||
@ -83,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"))
|
||||
|
||||
|
||||
|
@ -14,6 +14,8 @@ sys.path.append('../src/')
|
||||
project = 'PyAPplus64'
|
||||
copyright = '2023, Thomas Tuerk'
|
||||
author = 'Thomas Tuerk'
|
||||
version = '1.1.0'
|
||||
release = '1.1.0'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
@ -13,24 +13,35 @@ das Deploy-, das Test- und das Prod-System. Ein Beispiel ist im Unterverzeichnis
|
||||
|
||||
.. literalinclude:: ../../examples/applus-server.yaml
|
||||
:language: yaml
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
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 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.
|
||||
|
||||
.. literalinclude:: ../../examples/applus_configs.py
|
||||
:language: python
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
|
||||
``read_settings.py``
|
||||
-----------------------
|
||||
Einfaches Beispiel für Auslesen der SysConf und bestimmter Einstellungen.
|
||||
|
||||
.. literalinclude:: ../../examples/read_settings.py
|
||||
:language: python
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
``check_dokumente.py``
|
||||
-----------------------
|
||||
Einfaches Beispiel für lesenden und schreibenden Zugriff auf APplus Datenbank.
|
||||
|
||||
.. literalinclude:: ../../examples/check_dokumente.py
|
||||
:language: python
|
||||
:lines: 6-
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
|
||||
@ -40,7 +51,7 @@ Sehr einfaches Beispiel zur Erstellung einer Excel-Tabelle aus einer SQL-Abfrage
|
||||
|
||||
.. literalinclude:: ../../examples/adhoc_report.py
|
||||
:language: python
|
||||
:lines: 7-
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
|
||||
@ -60,7 +71,7 @@ Die GUI wird um die Erzeugung von Excel-Dateien mit Mengenabweichungen gebaut.
|
||||
|
||||
.. literalinclude:: ../../examples/mengenabweichung_gui.pyw
|
||||
:language: python
|
||||
:lines: 7-
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
``copy_artikel.py``
|
||||
@ -69,5 +80,5 @@ Beispiel, wie Artikel inklusive Arbeitsplan und Stückliste dupliziert werden ka
|
||||
|
||||
.. literalinclude:: ../../examples/copy_artikel.py
|
||||
:language: python
|
||||
:lines: 21-
|
||||
:lines: 22-
|
||||
:linenos:
|
||||
|
@ -10,6 +10,7 @@ import PyAPplus64
|
||||
import applus_configs
|
||||
import pathlib
|
||||
|
||||
|
||||
def main(confFile: pathlib.Path, outfile: str) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
|
@ -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",
|
||||
|
@ -14,4 +14,3 @@ configdir = basedir.joinpath("config")
|
||||
serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
|
||||
serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
|
||||
serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
|
||||
|
||||
|
@ -9,23 +9,29 @@
|
||||
import pathlib
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
from typing import Optional
|
||||
|
||||
def main(confFile : pathlib.Path, docDir:str, updateDB:bool) -> None:
|
||||
|
||||
def main(confFile: pathlib.Path, updateDB: bool, docDir: Optional[str] = None) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("ARTIKEL");
|
||||
sql.addFields("ID", "ARTIKEL", "DOCUMENTS");
|
||||
sql.where.addConditionFieldStringNotEmpty("DOCUMENTS");
|
||||
if docDir is None:
|
||||
docDir = str(server.scripttool.getInstallPathWebServer().joinpath("DocLib"))
|
||||
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("ARTIKEL")
|
||||
sql.addFields("ID", "ARTIKEL", "DOCUMENTS")
|
||||
sql.where.addConditionFieldStringNotEmpty("DOCUMENTS")
|
||||
|
||||
for row in server.dbQueryAll(sql):
|
||||
doc = pathlib.Path(docDir + row.DOCUMENTS);
|
||||
doc = pathlib.Path(docDir + row.DOCUMENTS)
|
||||
if not doc.exists():
|
||||
print("Bild '{}' für Artikel '{}' nicht gefunden".format(doc, row.ARTIKEL))
|
||||
|
||||
if updateDB:
|
||||
upd = server.mkUseXMLRowUpdate("ARTIKEL", row.ID);
|
||||
upd.addField("DOCUMENTS", None);
|
||||
upd.update();
|
||||
upd = server.mkUseXMLRowUpdate("ARTIKEL", row.ID)
|
||||
upd.addField("DOCUMENTS", None)
|
||||
upd.update()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest, "somedir\\WebServer\\DocLib", False)
|
||||
main(applus_configs.serverConfYamlTest, False)
|
||||
|
@ -24,18 +24,19 @@ import PyAPplus64
|
||||
import applus_configs
|
||||
import logging
|
||||
import yaml
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def main(confFile:pathlib.Path, artikel:str, artikelNeu:str|None=None) -> None:
|
||||
def main(confFile: pathlib.Path, artikel: str, artikelNeu: Optional[str] = None) -> None:
|
||||
# Server verbinden
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
# DuplicateBusinessObject für Artikel erstellen
|
||||
dArt = PyAPplus64.duplicate.loadDBDuplicateArtikel(server, artikel);
|
||||
dArt = PyAPplus64.duplicate.loadDBDuplicateArtikel(server, artikel)
|
||||
|
||||
# DuplicateBusinessObject zur Demonstration in YAML konvertieren und zurück
|
||||
dArtYaml = yaml.dump(dArt);
|
||||
print(dArtYaml);
|
||||
dArtYaml = yaml.dump(dArt)
|
||||
print(dArtYaml)
|
||||
dArt2 = yaml.load(dArtYaml, Loader=yaml.UnsafeLoader)
|
||||
|
||||
# Neue Artikel-Nummer bestimmen und DuplicateBusinessObject in DB schreiben
|
||||
@ -45,8 +46,8 @@ def main(confFile:pathlib.Path, artikel:str, artikelNeu:str|None=None) -> None:
|
||||
|
||||
if not (dArt is None):
|
||||
dArt.setFields({"artikel": artikelNeu})
|
||||
res = dArt.insert(server);
|
||||
print(res);
|
||||
res = dArt.insert(server)
|
||||
print(res)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@ -56,4 +57,3 @@ if __name__ == "__main__":
|
||||
# logger.setLevel(logging.ERROR)
|
||||
|
||||
main(applus_configs.serverConfYamlTest, "my-artikel", artikelNeu="my-artikel-copy")
|
||||
|
||||
|
@ -13,12 +13,13 @@ import PyAPplus64
|
||||
import applus_configs
|
||||
import pandas as pd # type: ignore
|
||||
import pathlib
|
||||
from typing import *
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
|
||||
def ladeAlleWerkstattauftragMengenabweichungen(
|
||||
server: PyAPplus64.APplusServer,
|
||||
cond:PyAPplus64.SqlCondition|str|None=None) -> pd.DataFrame:
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAG w");
|
||||
cond: Union[PyAPplus64.SqlCondition, str, None] = None) -> pd.DataFrame:
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAG w")
|
||||
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
||||
|
||||
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION")
|
||||
@ -31,18 +32,20 @@ def ladeAlleWerkstattauftragMengenabweichungen(
|
||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||
sql.where.addCondition(cond)
|
||||
sql.order = "w.UPDDATE"
|
||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql);
|
||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql)
|
||||
|
||||
# Add Links
|
||||
df = dfOrg.copy();
|
||||
df = df.drop(columns=["ID"]);
|
||||
df = dfOrg.copy()
|
||||
df = df.drop(columns=["ID"])
|
||||
# df = df[['POSITION', 'BAUFTRAG', 'MENGE']] # reorder / filter columns
|
||||
|
||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.POSITION,
|
||||
lambda r: server.makeWebLinkWauftrag(
|
||||
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.BAUFTRAG,
|
||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||
|
||||
@ -57,15 +60,15 @@ def ladeAlleWerkstattauftragMengenabweichungen(
|
||||
"UPDDATE": "geändert am",
|
||||
"UPDNAME": "geändert von"
|
||||
}
|
||||
df.rename(columns=colNames, inplace=True);
|
||||
df.rename(columns=colNames, inplace=True)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def ladeAlleWerkstattauftragPosMengenabweichungen(
|
||||
server: PyAPplus64.APplusServer,
|
||||
cond:PyAPplus64.SqlCondition|str|None=None) -> pd.DataFrame:
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAGPOS w");
|
||||
cond: Union[PyAPplus64.SqlCondition, str, None] = None) -> pd.DataFrame:
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAGPOS w")
|
||||
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
||||
|
||||
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION", "AG")
|
||||
@ -78,19 +81,22 @@ def ladeAlleWerkstattauftragPosMengenabweichungen(
|
||||
sql.where.addCondition(cond)
|
||||
sql.order = "w.UPDDATE"
|
||||
|
||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql);
|
||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql)
|
||||
|
||||
# Add Links
|
||||
df = dfOrg.copy();
|
||||
df = df.drop(columns=["ID"]);
|
||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
df = dfOrg.copy()
|
||||
df = df.drop(columns=["ID"])
|
||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.POSITION,
|
||||
lambda r: server.makeWebLinkWauftrag(
|
||||
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.BAUFTRAG,
|
||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.AG,
|
||||
lambda r: server.makeWebLinkWauftragPos(
|
||||
bauftrag=r.BAUFTRAG, position=r.POSITION, accessid=r.ID))
|
||||
@ -111,11 +117,12 @@ def ladeAlleWerkstattauftragPosMengenabweichungen(
|
||||
"UPDDATE": "geändert am",
|
||||
"UPDNAME": "geändert von"
|
||||
}
|
||||
df.rename(columns=colNames, inplace=True);
|
||||
df.rename(columns=colNames, inplace=True)
|
||||
return df
|
||||
|
||||
def computeInYearMonthCond(field : str, year:int|None=None,
|
||||
month:int|None=None) -> PyAPplus64.SqlCondition | None:
|
||||
|
||||
def computeInYearMonthCond(field: str, year: Optional[int] = None,
|
||||
month: Optional[int] = None) -> Optional[PyAPplus64.SqlCondition]:
|
||||
if not (year is None):
|
||||
if month is None:
|
||||
return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInYear(field, year)
|
||||
@ -124,52 +131,59 @@ def computeInYearMonthCond(field : str, year:int|None=None,
|
||||
else:
|
||||
return None
|
||||
|
||||
def computeFileName(year:int|None=None, month:int|None=None) -> str:
|
||||
|
||||
def computeFileName(year: Optional[int] = None, month: Optional[int] = None) -> str:
|
||||
if year is None:
|
||||
return 'mengenabweichungen-all.xlsx';
|
||||
return 'mengenabweichungen-all.xlsx'
|
||||
else:
|
||||
if month is None:
|
||||
return 'mengenabweichungen-{:04d}.xlsx'.format(year);
|
||||
return 'mengenabweichungen-{:04d}.xlsx'.format(year)
|
||||
else:
|
||||
return 'mengenabweichungen-{:04d}-{:02d}.xlsx'.format(year, month);
|
||||
return 'mengenabweichungen-{:04d}-{:02d}.xlsx'.format(year, month)
|
||||
|
||||
|
||||
def _exportInternal(server: PyAPplus64.APplusServer, fn: str,
|
||||
cond: Union[PyAPplus64.SqlCondition, str, None]) -> int:
|
||||
df1 = ladeAlleWerkstattauftragMengenabweichungen(server, cond)
|
||||
df2 = ladeAlleWerkstattauftragPosMengenabweichungen(server, cond)
|
||||
print ("erzeuge " + fn);
|
||||
print("erzeuge " + fn)
|
||||
PyAPplus64.pandas.exportToExcel(fn, [(df1, "WAuftrag"), (df2, "WAuftrag-Pos")], addTable=True)
|
||||
return len(df1.index) + len(df2.index)
|
||||
|
||||
|
||||
def exportVonBis(server: PyAPplus64.APplusServer, fn: str,
|
||||
von:datetime.datetime|None, bis:datetime.datetime|None) -> int:
|
||||
von: Optional[datetime.datetime], bis: Optional[datetime.datetime]) -> int:
|
||||
cond = PyAPplus64.sql_utils.SqlConditionDateTimeFieldInRange("w.UPDDATE", von, bis)
|
||||
return _exportInternal(server, fn, cond)
|
||||
|
||||
|
||||
def exportYearMonth(server: PyAPplus64.APplusServer,
|
||||
year:int|None=None, month:int|None=None) -> int:
|
||||
year: Optional[int] = None, month: Optional[int] = None) -> int:
|
||||
cond = computeInYearMonthCond("w.UPDDATE", year=year, month=month)
|
||||
fn = computeFileName(year=year, month=month)
|
||||
return _exportInternal(server, fn, cond)
|
||||
|
||||
|
||||
def computePreviousMonthYear(cyear: int, cmonth: int) -> Tuple[int, int]:
|
||||
if cmonth == 1:
|
||||
return (cyear-1, 12)
|
||||
else:
|
||||
return (cyear, cmonth-1);
|
||||
return (cyear, cmonth-1)
|
||||
|
||||
|
||||
def computeNextMonthYear(cyear: int, cmonth: int) -> Tuple[int, int]:
|
||||
if cmonth == 12:
|
||||
return (cyear+1, 1)
|
||||
else:
|
||||
return (cyear, cmonth+1);
|
||||
return (cyear, cmonth+1)
|
||||
|
||||
def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) -> None:
|
||||
|
||||
def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
now = datetime.date.today()
|
||||
(cmonth, cyear) = (now.month, now.year)
|
||||
(pyear, pmonth) = computePreviousMonthYear(cyear, cmonth);
|
||||
(pyear, pmonth) = computePreviousMonthYear(cyear, cmonth)
|
||||
|
||||
# Ausgaben
|
||||
exportYearMonth(server, cyear, cmonth) # Aktueller Monat
|
||||
@ -178,5 +192,6 @@ def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) ->
|
||||
# export(cyear-1) # letztes Jahr
|
||||
# export() # alles
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest)
|
||||
|
@ -12,9 +12,10 @@ import datetime
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import pathlib
|
||||
from typing import *
|
||||
from typing import Tuple, Optional, Union
|
||||
|
||||
def parseDate (dateS:str) -> Tuple[datetime.datetime|None, bool]:
|
||||
|
||||
def parseDate(dateS: str) -> Tuple[Optional[datetime.datetime], bool]:
|
||||
if dateS is None or dateS == '':
|
||||
return (None, True)
|
||||
else:
|
||||
@ -24,12 +25,15 @@ def parseDate (dateS:str) -> Tuple[datetime.datetime|None, bool]:
|
||||
sg.popup_error("Fehler beim Parsen des Datums '{}'".format(dateS))
|
||||
return (None, False)
|
||||
|
||||
|
||||
def createFile(server: PyAPplus64.APplusServer, fileS: str, vonS: str, bisS: str) -> None:
|
||||
(von, vonOK) = parseDate(vonS)
|
||||
if not vonOK: return
|
||||
if not vonOK:
|
||||
return
|
||||
|
||||
(bis, bisOK) = parseDate(bisS)
|
||||
if not bisOK: return
|
||||
if not bisOK:
|
||||
return
|
||||
|
||||
if (fileS is None) or fileS == '':
|
||||
sg.popup_error("Es wurde keine Ausgabedatei ausgewählt.")
|
||||
@ -41,7 +45,7 @@ def createFile(server:PyAPplus64.APplusServer, fileS:str, vonS:str, bisS:str)->N
|
||||
sg.popup_ok("{} Datensätze erfolgreich in Datei '{}' geschrieben.".format(c, file))
|
||||
|
||||
|
||||
def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) -> None:
|
||||
def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
layout = [
|
||||
@ -54,7 +58,8 @@ def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) ->
|
||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||
target="Bis", format='%d.%m.%Y')],
|
||||
[sg.Text('Ausgabedatei', size=(15, 1)), sg.InputText(key='File'),
|
||||
sg.FileSaveAs(button_text="wählen", target="File",
|
||||
sg.FileSaveAs(button_text="wählen",
|
||||
target="File",
|
||||
file_types=(('Excel Files', '*.xlsx'),),
|
||||
default_extension=".xlsx")],
|
||||
[sg.Button("Aktueller Monat"), sg.Button("Letzter Monat"),
|
||||
@ -66,33 +71,34 @@ def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) ->
|
||||
window = sg.Window("Mengenabweichung " + systemName, layout)
|
||||
now = datetime.date.today()
|
||||
(cmonth, cyear) = (now.month, now.year)
|
||||
(pyear, pmonth) = mengenabweichung.computePreviousMonthYear(cyear, cmonth);
|
||||
(nyear, nmonth) = mengenabweichung.computeNextMonthYear(cyear, cmonth);
|
||||
(pyear, pmonth) = mengenabweichung.computePreviousMonthYear(cyear, cmonth)
|
||||
(nyear, nmonth) = mengenabweichung.computeNextMonthYear(cyear, cmonth)
|
||||
|
||||
while True:
|
||||
event, values = window.read()
|
||||
if event == sg.WIN_CLOSED or event == 'Beenden':
|
||||
break
|
||||
if event == 'Aktueller Monat':
|
||||
window['Von'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear));
|
||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(nmonth, nyear));
|
||||
window['Von'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear))
|
||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(nmonth, nyear))
|
||||
if event == 'Letzter Monat':
|
||||
window['Von'].update(value="01.{:02d}.{:04d}".format(pmonth, pyear));
|
||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear));
|
||||
window['Von'].update(value="01.{:02d}.{:04d}".format(pmonth, pyear))
|
||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear))
|
||||
if event == 'Aktuelles Jahr':
|
||||
window['Von'].update(value="01.01.{:04d}".format(cyear));
|
||||
window['Bis'].update(value="01.01.{:04d}".format(cyear+1));
|
||||
window['Von'].update(value="01.01.{:04d}".format(cyear))
|
||||
window['Bis'].update(value="01.01.{:04d}".format(cyear+1))
|
||||
if event == 'Letztes Jahr':
|
||||
window['Von'].update(value="01.01.{:04d}".format(cyear-1));
|
||||
window['Bis'].update(value="01.01.{:04d}".format(cyear));
|
||||
window['Von'].update(value="01.01.{:04d}".format(cyear-1))
|
||||
window['Bis'].update(value="01.01.{:04d}".format(cyear))
|
||||
if event == 'Speichern':
|
||||
try:
|
||||
createFile(server, values.get('File', None),
|
||||
values.get('Von', None), values.get('Bis', None))
|
||||
except Exception as e:
|
||||
sg.popup_error_with_traceback("Beim Erzeugen der Excel-Datei trat ein Fehler auf:", e);
|
||||
sg.popup_error_with_traceback("Beim Erzeugen der Excel-Datei trat ein Fehler auf:", e)
|
||||
|
||||
window.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlProd)
|
||||
|
55
examples/read_settings.py
Normal file
55
examples/read_settings.py
Normal file
@ -0,0 +1,55 @@
|
||||
# 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.
|
||||
|
||||
# Einfaches Script, das verschiedene Werte des Servers ausliest.
|
||||
# Dies sind SysConfig-Einstellungen, aber auch der aktuelle Mandant,
|
||||
# Systemnamen, ...
|
||||
|
||||
import pathlib
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
print("\n\nSysConf Lookups:")
|
||||
|
||||
print(" Default Auftragsart:", server.sysconf.getString("STAMM", "DEFAULTAUFTRAGSART"))
|
||||
print(" Auftragsarten:")
|
||||
arten = server.sysconf.getList("STAMM", "AUFTRAGSART", sep='\n')
|
||||
if not arten:
|
||||
arten = []
|
||||
for a in arten:
|
||||
print(" - " + a)
|
||||
|
||||
print(" Firmen-Nr. automatisch vergeben:", server.sysconf.getBoolean("STAMM", "FIRMAAUTOMATIK"))
|
||||
print(" Anzahl Artikelstellen:", server.sysconf.getInt("STAMM", "ARTKLASSIFNRLAENGE"))
|
||||
|
||||
print("\n\nScriptTool:")
|
||||
|
||||
print(" CurrentDate:", server.scripttool.getCurrentDate())
|
||||
print(" CurrentTime:", server.scripttool.getCurrentTime())
|
||||
print(" CurrentDateTime:", server.scripttool.getCurrentDateTime())
|
||||
print(" LoginName:", server.scripttool.getLoginName())
|
||||
print(" UserName:", server.scripttool.getUserName())
|
||||
print(" UserFullName:", server.scripttool.getUserFullName())
|
||||
print(" SystemName:", server.scripttool.getSystemName())
|
||||
print(" Mandant:", server.scripttool.getMandant())
|
||||
print(" MandantName:", server.scripttool.getMandantName())
|
||||
print(" InstallPath:", server.scripttool.getInstallPath())
|
||||
print(" InstallPathAppServer:", server.scripttool.getInstallPathAppServer())
|
||||
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)
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "PyAPplus64"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
authors = [
|
||||
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
|
||||
]
|
||||
|
@ -6,8 +6,6 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from . import applus_db
|
||||
from . import applus_server
|
||||
from . import applus_sysconf
|
||||
@ -18,13 +16,12 @@ import yaml
|
||||
import urllib.parse
|
||||
from zeep import Client
|
||||
import pyodbc # type: ignore
|
||||
from typing import *
|
||||
from typing import TYPE_CHECKING, Optional, Any, Callable, Dict, Sequence, Set, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import FileDescriptorOrPath
|
||||
|
||||
|
||||
|
||||
class APplusServer:
|
||||
"""
|
||||
Verbindung zu einem APplus DB und App Server mit Hilfsfunktionen für den komfortablen Zugriff.
|
||||
@ -36,13 +33,15 @@ class APplusServer:
|
||||
:param web_settings: die Einstellungen für die Verbindung mit dem APplus Web Server
|
||||
:type web_settings: APplusWebServerSettings
|
||||
"""
|
||||
def __init__(self, db_settings : applus_db.APplusDBSettings, server_settings : applus_server.APplusAppServerSettings, web_settings : applus_server.APplusWebServerSettings):
|
||||
def __init__(self,
|
||||
db_settings: applus_db.APplusDBSettings,
|
||||
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()
|
||||
"""
|
||||
@ -51,19 +50,18 @@ class APplusServer:
|
||||
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"""
|
||||
|
||||
self.sysconf : applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self);
|
||||
self.sysconf: applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self)
|
||||
"""erlaubt den Zugriff auf die Sysconfig"""
|
||||
|
||||
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"""
|
||||
|
||||
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:
|
||||
@ -86,13 +84,13 @@ class APplusServer:
|
||||
if raw:
|
||||
return str(sql)
|
||||
else:
|
||||
return self.client_table.service.getCompleteSQL(sql);
|
||||
return self.client_table.service.getCompleteSQL(sql)
|
||||
|
||||
def dbQueryAll(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False,
|
||||
apply: Optional[Callable[[pyodbc.Row], Any]] = None) -> Any:
|
||||
"""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."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
sqlC = self.completeSQL(sql, raw=raw)
|
||||
return applus_db.rawQueryAll(self.db_conn, sqlC, *args, apply=apply)
|
||||
|
||||
def dbQuerySingleValues(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Sequence[Any]:
|
||||
@ -102,38 +100,38 @@ class APplusServer:
|
||||
def dbQuery(self, sql: sql_utils.SqlStatement, f: Callable[[pyodbc.Row], None], *args: Any, raw: bool = False) -> None:
|
||||
"""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."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
sqlC = self.completeSQL(sql, raw=raw)
|
||||
applus_db.rawQuery(self.db_conn, sqlC, f, *args)
|
||||
|
||||
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."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
sqlC = self.completeSQL(sql, raw=raw)
|
||||
return applus_db.rawQuerySingleRow(self.db_conn, sqlC, *args)
|
||||
|
||||
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.
|
||||
Diese Zeile wird als Dictionary geliefert."""
|
||||
row = self.dbQuerySingleRow(sql, *args, raw=raw);
|
||||
row = self.dbQuerySingleRow(sql, *args, raw=raw)
|
||||
if row:
|
||||
return applus_db.row_to_dict(row);
|
||||
return applus_db.row_to_dict(row)
|
||||
else:
|
||||
return None
|
||||
|
||||
def dbQuerySingleValue(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Any:
|
||||
"""Führt eine SQL Query aus, die maximal einen Wert zurückliefern soll.
|
||||
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)
|
||||
|
||||
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=?"
|
||||
c = self.dbQuerySingleValue(sql, table);
|
||||
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
|
||||
@ -146,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]:
|
||||
"""
|
||||
@ -158,14 +169,14 @@ class APplusServer:
|
||||
:rtype: {str}
|
||||
"""
|
||||
sql = sql_utils.SqlStatementSelect("SYS.TABLES T")
|
||||
join = sql.addInnerJoin("SYS.COLUMNS C");
|
||||
join = sql.addInnerJoin("SYS.COLUMNS C")
|
||||
join.on.addConditionFieldsEq("T.Object_ID", "C.Object_ID")
|
||||
if not (isComputed == None):
|
||||
if not (isComputed is None):
|
||||
join.on.addConditionFieldEq("c.is_computed", isComputed)
|
||||
sql.addFields("C.NAME")
|
||||
|
||||
sql.where.addConditionFieldEq("t.name", sql_utils.SqlParam())
|
||||
return sql_utils.normaliseDBfieldSet(self.dbQueryAll(sql, table, apply=lambda r : r.NAME));
|
||||
return sql_utils.normaliseDBfieldSet(self.dbQueryAll(sql, table, apply=lambda r: r.NAME))
|
||||
|
||||
def getUniqueFieldsOfTable(self, table: str) -> Dict[str, List[str]]:
|
||||
"""
|
||||
@ -176,11 +187,9 @@ class APplusServer:
|
||||
"""
|
||||
return applus_db.getUniqueFieldsOfTable(self.db_conn, table)
|
||||
|
||||
|
||||
def useXML(self, xml: str) -> Any:
|
||||
"""Ruft ``p2core.xml.usexml`` auf. Wird meist durch ein ``UseXMLRow-Objekt`` aufgerufen."""
|
||||
return self.client_xml.service.useXML(xml);
|
||||
|
||||
return self.client_xml.service.useXML(xml)
|
||||
|
||||
def mkUseXMLRowInsert(self, table: str) -> applus_usexml.UseXmlRowInsert:
|
||||
"""
|
||||
@ -209,13 +218,12 @@ class APplusServer:
|
||||
|
||||
return applus_usexml.UseXmlRowInsertOrUpdate(self, table)
|
||||
|
||||
|
||||
def mkUseXMLRowDelete(self, table: str, id: int) -> applus_usexml.UseXmlRowDelete:
|
||||
return applus_usexml.UseXmlRowDelete(self, table, id)
|
||||
|
||||
def execUseXMLRowDelete(self, table: str, id: int) -> None:
|
||||
delRow = self.mkUseXMLRowDelete(table, id)
|
||||
delRow.delete();
|
||||
delRow.delete()
|
||||
|
||||
def nextNumber(self, obj: str) -> str:
|
||||
"""
|
||||
@ -224,30 +232,29 @@ class APplusServer:
|
||||
return self.client_nummer.service.nextNumber(obj)
|
||||
|
||||
def makeWebLink(self, base: str, **kwargs: Any) -> str:
|
||||
if not self.web_settings.baseurl:
|
||||
raise Exception("keine Webserver-BaseURL gesetzt");
|
||||
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 == None):
|
||||
if not (argv is None):
|
||||
if firstArg:
|
||||
firstArg = False;
|
||||
firstArg = False
|
||||
url += "?"
|
||||
else:
|
||||
url += "&"
|
||||
url += arg + "=" + urllib.parse.quote(str(argv))
|
||||
return url;
|
||||
return url
|
||||
|
||||
def makeWebLinkWauftragPos(self, **kwargs: Any) -> str:
|
||||
return self.makeWebLink("wp/wauftragPosRec.aspx", **kwargs);
|
||||
return self.makeWebLink("wp/wauftragPosRec.aspx", **kwargs)
|
||||
|
||||
def makeWebLinkWauftrag(self, **kwargs: Any) -> str:
|
||||
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs);
|
||||
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs)
|
||||
|
||||
def makeWebLinkBauftrag(self, **kwargs: Any) -> str:
|
||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs);
|
||||
|
||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs)
|
||||
|
||||
|
||||
def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||
@ -256,20 +263,22 @@ def applusFromConfigDict(yamlDict:Dict[str, Any], user:Optional[str]=None, env:O
|
||||
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);
|
||||
password=yamlDict["dbserver"]["password"])
|
||||
return APplusServer(dbparams, server_settings)
|
||||
|
||||
|
||||
def applusFromConfigFile(yamlfile: 'FileDescriptorOrPath',
|
||||
user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||
@ -280,8 +289,8 @@ def applusFromConfigFile(yamlfile : 'FileDescriptorOrPath',
|
||||
|
||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||
|
||||
|
||||
def applusFromConfig(yamlString: str, user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||
yamlDict = yaml.safe_load(yamlString)
|
||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||
|
||||
|
@ -6,16 +6,15 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
import pyodbc # type: ignore
|
||||
import logging
|
||||
from .sql_utils import SqlStatement
|
||||
from . import sql_utils
|
||||
from typing import *
|
||||
from typing import List, Dict, Set, Any, Optional, Callable, Sequence
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__);
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APplusDBSettings:
|
||||
"""
|
||||
@ -24,11 +23,10 @@ class APplusDBSettings:
|
||||
|
||||
def __init__(self, server: str, database: str, user: str, password: str):
|
||||
self.server = server
|
||||
self.database = database;
|
||||
self.database = database
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
|
||||
def getConnectionString(self) -> str:
|
||||
"""Liefert den ODBC Connection-String für die Verbindung.
|
||||
:return: den Connection-String
|
||||
@ -45,17 +43,18 @@ class APplusDBSettings:
|
||||
return pyodbc.connect(self.getConnectionString())
|
||||
|
||||
|
||||
|
||||
def row_to_dict(row: pyodbc.Row) -> Dict[str, Any]:
|
||||
"""Konvertiert eine Zeile in ein Dictionary"""
|
||||
return dict(zip([t[0] for t in row.cursor_description], row))
|
||||
|
||||
|
||||
def _logSQLWithArgs(sql: SqlStatement, *args: Any) -> None:
|
||||
if args:
|
||||
logger.debug("executing '{}' with args {}".format(str(sql), str(args)))
|
||||
else:
|
||||
logger.debug("executing '{}'".format(str(sql)))
|
||||
|
||||
|
||||
def rawQueryAll(
|
||||
cnxn: pyodbc.Connection,
|
||||
sql: SqlStatement,
|
||||
@ -69,42 +68,46 @@ def rawQueryAll(
|
||||
with cnxn.cursor() as cursor:
|
||||
cursor.execute(str(sql), *args)
|
||||
|
||||
rows = cursor.fetchall();
|
||||
rows = cursor.fetchall()
|
||||
if apply is None:
|
||||
return rows
|
||||
else:
|
||||
res = []
|
||||
for r in rows:
|
||||
rr = apply(r)
|
||||
if not (rr == None):
|
||||
if not (rr is None):
|
||||
res.append(rr)
|
||||
return res
|
||||
|
||||
|
||||
def rawQuery(cnxn: pyodbc.Connection, sql: sql_utils.SqlStatement, f: Callable[[pyodbc.Row], None], *args: Any) -> None:
|
||||
"""Führt eine SQL Query direkt aus und führt für jede Zeile die übergeben Funktion aus."""
|
||||
_logSQLWithArgs(sql, *args)
|
||||
with cnxn.cursor() as cursor:
|
||||
cursor.execute(str(sql), *args)
|
||||
for row in cursor:
|
||||
f(row);
|
||||
f(row)
|
||||
|
||||
|
||||
def rawQuerySingleRow(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any) -> Optional[pyodbc.Row]:
|
||||
"""Führt eine SQL Query direkt aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
||||
_logSQLWithArgs(sql, *args)
|
||||
with cnxn.cursor() as cursor:
|
||||
cursor.execute(str(sql), *args)
|
||||
return cursor.fetchone();
|
||||
return cursor.fetchone()
|
||||
|
||||
|
||||
def rawQuerySingleValue(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any) -> Any:
|
||||
"""Führt eine SQL Query direkt aus, die maximal einen Wert zurückliefern soll. Dieser Wert oder None wird geliefert."""
|
||||
_logSQLWithArgs(sql, *args)
|
||||
with cnxn.cursor() as cursor:
|
||||
cursor.execute(str(sql), *args)
|
||||
row = cursor.fetchone();
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row[0];
|
||||
return row[0]
|
||||
else:
|
||||
return None;
|
||||
return None
|
||||
|
||||
|
||||
def getUniqueFieldsOfTable(cnxn: pyodbc.Connection, table: str) -> Dict[str, List[str]]:
|
||||
"""
|
||||
@ -149,7 +152,7 @@ class DBTableIDs():
|
||||
"""
|
||||
table = table.upper()
|
||||
if not (table in self.data):
|
||||
self.data[table] = set(ids);
|
||||
self.data[table] = set(ids)
|
||||
else:
|
||||
self.data[table].update(ids)
|
||||
|
||||
@ -167,5 +170,3 @@ class DBTableIDs():
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.data)
|
||||
|
||||
|
||||
|
@ -6,12 +6,12 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .applus import APplusServer
|
||||
from . import sql_utils
|
||||
import lxml.etree as ET # type: ignore
|
||||
from typing import *
|
||||
from typing import Optional, Tuple, Set
|
||||
import pathlib
|
||||
|
||||
|
||||
class XMLDefinition:
|
||||
"""Repräsentation eines XML-Dokuments"""
|
||||
@ -31,10 +31,10 @@ class XMLDefinition:
|
||||
:rtype: Tuple[Set[str], bool]
|
||||
"""
|
||||
res: Set[str] = set()
|
||||
excl = True;
|
||||
excl = True
|
||||
dupl = self.root.find("duplicate")
|
||||
if (dupl is None):
|
||||
return (res, excl);
|
||||
return (res, excl)
|
||||
|
||||
exclS = dupl.get("type", default="exclude")
|
||||
excl = exclS.casefold() == "exclude"
|
||||
@ -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()
|
||||
@ -80,6 +80,24 @@ class APplusScriptTool:
|
||||
def getSystemName(self) -> str:
|
||||
return self.client.service.getSystemName()
|
||||
|
||||
def getInstallPath(self) -> str:
|
||||
"""
|
||||
Liefert den Installionspfad des Appservers
|
||||
"""
|
||||
return self.client.service.getInstallPath()
|
||||
|
||||
def getInstallPathAppServer(self) -> pathlib.Path:
|
||||
"""
|
||||
Liefert den Installionspfad des Appservers als PathLib-Path
|
||||
"""
|
||||
return pathlib.Path(self.getInstallPath())
|
||||
|
||||
def getInstallPathWebServer(self) -> pathlib.Path:
|
||||
"""
|
||||
Liefert den Installionspfad des Webservers als PathLib-Path
|
||||
"""
|
||||
return self.getInstallPathAppServer().parents[0].joinpath("WebServer")
|
||||
|
||||
def getXMLDefinitionString(self, obj: str, mandant: str = "") -> str:
|
||||
"""
|
||||
Läd die XML-Defintion als String vom APPServer. Auch wenn kein XML-Dokument im Dateisystem gefunden wird,
|
||||
@ -103,7 +121,7 @@ class APplusScriptTool:
|
||||
:type obj: str
|
||||
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
||||
:type mandant: str optional
|
||||
:return: das gefundene und mittels ElementTree geparste XML-Dokument
|
||||
:return: das gefundene und geparste XML-Dokument
|
||||
:rtype: ET.Element
|
||||
"""
|
||||
return ET.fromstring(self.getXMLDefinitionString(obj, mandant=mandant))
|
||||
@ -118,22 +136,21 @@ class APplusScriptTool:
|
||||
:type obj: str
|
||||
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
||||
:type mandant: str optional
|
||||
:return: das gefundene und mittels ElementTree geparste XML-Dokument
|
||||
:return: das gefundene und geparste XML-Dokument
|
||||
:rtype: Optional[XMLDefinition]
|
||||
"""
|
||||
e = self.getXMLDefinition(obj, mandant=mandant);
|
||||
e = self.getXMLDefinition(obj, mandant=mandant)
|
||||
if e is None:
|
||||
return None
|
||||
|
||||
if e.find("md5") is None:
|
||||
return None;
|
||||
return None
|
||||
|
||||
o = e.find("object")
|
||||
if o is None:
|
||||
return None
|
||||
else:
|
||||
return XMLDefinition(o);
|
||||
|
||||
return XMLDefinition(o)
|
||||
|
||||
def getMandant(self) -> str:
|
||||
"""
|
||||
@ -146,3 +163,21 @@ class APplusScriptTool:
|
||||
Liefert den Namen des aktuellen Mandanten
|
||||
"""
|
||||
return self.client.service.getCurrentClientProperty("NAME")
|
||||
|
||||
def getServerInfoString(self) -> str:
|
||||
"""
|
||||
Liefert Informationen zum Server als String. Dieser String repräsentiert ein XML Dokument.
|
||||
|
||||
:return: das XML-Dokument als String
|
||||
:rtype: str
|
||||
"""
|
||||
return self.client.service.getP2plusServerInfo()
|
||||
|
||||
def getServerInfo(self) -> Optional[ET.Element]:
|
||||
"""
|
||||
Liefert Informationen zum Server als ein XML Dokument.
|
||||
|
||||
:return: das gefundene und geparste XML-Dokument
|
||||
:rtype: ET.Element
|
||||
"""
|
||||
return ET.fromstring(self.getServerInfoString())
|
||||
|
@ -6,8 +6,6 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from requests import Session # type: ignore
|
||||
from requests.auth import HTTPBasicAuth # type: ignore # or HTTPDigestAuth, or OAuth1, etc.
|
||||
from zeep import Client
|
||||
@ -15,55 +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
|
||||
|
||||
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 == 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:
|
||||
userEnv = settings.user;
|
||||
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) + "/";
|
||||
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
|
||||
@ -77,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;
|
||||
return 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
|
||||
|
@ -6,9 +6,7 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from typing import *
|
||||
from typing import TYPE_CHECKING, Optional, Dict, Any, Callable, Sequence
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .applus import APplusServer
|
||||
@ -24,36 +22,36 @@ 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:
|
||||
self.cache = {};
|
||||
self.cache = {}
|
||||
|
||||
def _getGeneral(self, ty: str, f: Callable[[str, str], Any], module: str, name: str, useCache: bool) -> Any:
|
||||
cacheKey = module + "/" + name + "/" + ty;
|
||||
cacheKey = module + "/" + name + "/" + ty
|
||||
if useCache and cacheKey in self.cache:
|
||||
return self.cache[cacheKey]
|
||||
else:
|
||||
v = f(module, name);
|
||||
self.cache[cacheKey] = v;
|
||||
return v;
|
||||
v = f(module, name)
|
||||
self.cache[cacheKey] = v
|
||||
return v
|
||||
|
||||
def getString(self, module: str, name: str, useCache: bool = True) -> str:
|
||||
return self._getGeneral("string", self.client.service.getString, module, name, useCache);
|
||||
return self._getGeneral("string", self.client.service.getString, module, name, useCache)
|
||||
|
||||
def getInt(self, module: str, name: str, useCache: bool = True) -> int:
|
||||
return self._getGeneral("int", self.client.service.getInt, module, name, useCache);
|
||||
return self._getGeneral("int", self.client.service.getInt, module, name, useCache)
|
||||
|
||||
def getDouble(self, module: str, name: str, useCache: bool = True) -> float:
|
||||
return self._getGeneral("double", self.client.service.getDouble, module, name, useCache);
|
||||
return self._getGeneral("double", self.client.service.getDouble, module, name, useCache)
|
||||
|
||||
def getBoolean(self, module: str, name: str, useCache: bool = True) -> bool:
|
||||
return self._getGeneral("boolean", self.client.service.getBoolean, module, name, useCache);
|
||||
return self._getGeneral("boolean", self.client.service.getBoolean, module, name, useCache)
|
||||
|
||||
def getList(self, module: str, name: str, useCache: bool = True, sep: str = ",") -> Optional[Sequence[str]]:
|
||||
s = self.getString(module, name, useCache=useCache);
|
||||
if (s == None or s == ""):
|
||||
s = self.getString(module, name, useCache=useCache)
|
||||
if (s is None or s == ""):
|
||||
return None
|
||||
|
||||
return s.split(sep);
|
||||
return s.split(sep)
|
||||
|
@ -6,26 +6,23 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
import lxml.etree as ET # type: ignore
|
||||
from . import sql_utils
|
||||
import datetime
|
||||
from typing import *
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .applus import APplusServer
|
||||
|
||||
|
||||
|
||||
def _formatValueForXMLRow(v: Any) -> str:
|
||||
"""Hilfsfunktion zum Formatieren eines Wertes für XML"""
|
||||
if (v is None):
|
||||
return "";
|
||||
return ""
|
||||
if isinstance(v, (int, float)):
|
||||
return str(v);
|
||||
return str(v)
|
||||
elif isinstance(v, str):
|
||||
return v;
|
||||
return v
|
||||
elif isinstance(v, datetime.datetime):
|
||||
return v.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||
elif isinstance(v, datetime.date):
|
||||
@ -71,7 +68,7 @@ class UseXmlRow:
|
||||
|
||||
def _buildXML(self) -> ET.Element:
|
||||
"""Hilfsfunktion, die das eigentliche XML baut"""
|
||||
row = ET.Element("row", cmd=self.cmd, table=self.table, nsmap={ "dt" : "urn:schemas-microsoft-com:datatypes"});
|
||||
row = ET.Element("row", cmd=self.cmd, table=self.table, nsmap={"dt": "urn:schemas-microsoft-com:datatypes"})
|
||||
|
||||
for name, value in self.fields.items():
|
||||
child = ET.Element(name)
|
||||
@ -80,7 +77,6 @@ class UseXmlRow:
|
||||
|
||||
return row
|
||||
|
||||
|
||||
def toprettyxml(self) -> str:
|
||||
"""
|
||||
Gibt das formatierte XML aus. Dieses kann per useXML an den AppServer übergeben werden.
|
||||
@ -93,7 +89,7 @@ class UseXmlRow:
|
||||
|
||||
if name is None:
|
||||
return None
|
||||
name = sql_utils.normaliseDBfield(name);
|
||||
name = sql_utils.normaliseDBfield(name)
|
||||
|
||||
if name in self.fields:
|
||||
return self.fields[name]
|
||||
@ -116,7 +112,7 @@ class UseXmlRow:
|
||||
return False
|
||||
return True
|
||||
|
||||
def addField(self, name:str|None, value:Any) -> None:
|
||||
def addField(self, name: Optional[str], value: Any) -> None:
|
||||
"""
|
||||
Fügt ein Feld zum Row-Node hinzu.
|
||||
|
||||
@ -129,7 +125,6 @@ class UseXmlRow:
|
||||
|
||||
self.fields[sql_utils.normaliseDBfield(name)] = value
|
||||
|
||||
|
||||
def addTimestampField(self, id: int, ts: Optional[bytes] = None) -> None:
|
||||
"""
|
||||
Fügt ein Timestamp-Feld hinzu. Wird kein Timestamp übergeben, wird mittels der ID der aktuelle
|
||||
@ -146,13 +141,12 @@ class UseXmlRow:
|
||||
:type ts: bytes
|
||||
"""
|
||||
if ts is None:
|
||||
ts = self.applus.dbQuerySingleValue("select timestamp from " + self.table + " where id = ?", id);
|
||||
ts = self.applus.dbQuerySingleValue("select timestamp from " + self.table + " where id = ?", id)
|
||||
if ts:
|
||||
self.addField("timestamp", ts.hex());
|
||||
self.addField("timestamp", ts.hex())
|
||||
else:
|
||||
raise Exception("kein Eintrag in Tabelle '" + self.table + " mit ID " + str(id) + " gefunden")
|
||||
|
||||
|
||||
def addTimestampIDFields(self, id: int, ts: Optional[bytes] = None) -> None:
|
||||
"""
|
||||
Fügt ein Timestamp-Feld sowie ein Feld id hinzu. Wird kein Timestamp übergeben, wird mittels der ID der aktuelle
|
||||
@ -164,14 +158,14 @@ class UseXmlRow:
|
||||
:type ts: bytes
|
||||
"""
|
||||
self.addField("id", id)
|
||||
self.addTimestampField(id, ts=ts);
|
||||
self.addTimestampField(id, ts=ts)
|
||||
|
||||
def exec(self) -> Any:
|
||||
"""
|
||||
Führt die UseXmlRow mittels useXML aus. Je nach Art der Zeile wird etwas zurückgeliefert oder nicht.
|
||||
In jedem Fall kann eine Exception geworfen werden.
|
||||
"""
|
||||
return self.applus.useXML(self.toprettyxml());
|
||||
return self.applus.useXML(self.toprettyxml())
|
||||
|
||||
|
||||
class UseXmlRowInsert(UseXmlRow):
|
||||
@ -185,14 +179,14 @@ class UseXmlRowInsert(UseXmlRow):
|
||||
"""
|
||||
|
||||
def __init__(self, applus: 'APplusServer', table: str) -> None:
|
||||
super().__init__(applus, table, "insert");
|
||||
super().__init__(applus, table, "insert")
|
||||
|
||||
def insert(self) -> int:
|
||||
"""
|
||||
Führt das insert aus. Entweder wird dabei eine Exception geworfen oder die ID des neuen Eintrags zurückgegeben.
|
||||
Dies ist eine Umbenennung von :meth:`exec`.
|
||||
"""
|
||||
return super().exec();
|
||||
return super().exec()
|
||||
|
||||
|
||||
class UseXmlRowDelete(UseXmlRow):
|
||||
@ -212,16 +206,15 @@ class UseXmlRowDelete(UseXmlRow):
|
||||
"""
|
||||
|
||||
def __init__(self, applus: 'APplusServer', table: str, id: int, ts: Optional[bytes] = None) -> None:
|
||||
super().__init__(applus, table, "delete");
|
||||
self.addTimestampIDFields(id, ts=ts);
|
||||
|
||||
super().__init__(applus, table, "delete")
|
||||
self.addTimestampIDFields(id, ts=ts)
|
||||
|
||||
def delete(self) -> None:
|
||||
"""
|
||||
Führt das delete aus. Evtl. wird dabei eine Exception geworfen.
|
||||
Dies ist eine Umbenennung von :meth:`exec`.
|
||||
"""
|
||||
super().exec();
|
||||
super().exec()
|
||||
|
||||
|
||||
class UseXmlRowUpdate(UseXmlRow):
|
||||
@ -240,17 +233,15 @@ class UseXmlRowUpdate(UseXmlRow):
|
||||
"""
|
||||
|
||||
def __init__(self, applus: 'APplusServer', table: str, id: int, ts: Optional[bytes] = None) -> None:
|
||||
super().__init__(applus, table, "update");
|
||||
self.addTimestampIDFields(id, ts=ts);
|
||||
|
||||
super().__init__(applus, table, "update")
|
||||
self.addTimestampIDFields(id, ts=ts)
|
||||
|
||||
def update(self) -> None:
|
||||
"""
|
||||
Führt das update aus. Evtl. wird dabei eine Exception geworfen.
|
||||
Dies ist eine Umbenennung von :meth:`exec`.
|
||||
"""
|
||||
super().exec();
|
||||
|
||||
super().exec()
|
||||
|
||||
|
||||
class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||
@ -268,20 +259,19 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||
"""
|
||||
|
||||
def __init__(self, applus: 'APplusServer', table: str) -> None:
|
||||
super().__init__(applus, table, "");
|
||||
super().__init__(applus, table, "")
|
||||
|
||||
|
||||
def checkExists(self) -> int|None:
|
||||
def checkExists(self) -> Optional[int]:
|
||||
"""
|
||||
Prüft, ob der Datensatz bereits in der DB existiert.
|
||||
Ist dies der Fall, wird die ID geliefert, sonst None
|
||||
"""
|
||||
|
||||
# Baue Bedingung
|
||||
cond = sql_utils.SqlConditionOr();
|
||||
cond = sql_utils.SqlConditionOr()
|
||||
for idx, fs in self.applus.getUniqueFieldsOfTable(self.table).items():
|
||||
if (self.checkFieldsSet(*fs)):
|
||||
condIdx = sql_utils.SqlConditionAnd();
|
||||
condIdx = sql_utils.SqlConditionAnd()
|
||||
for f in fs:
|
||||
condIdx.addConditionFieldEq(f, self.getField(f))
|
||||
cond.addCondition(condIdx)
|
||||
@ -296,7 +286,7 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||
r = UseXmlRowInsert(self.applus, self.table)
|
||||
for k, v in self.fields.items():
|
||||
r.addField(k, v)
|
||||
return r.insert();
|
||||
return r.insert()
|
||||
|
||||
def update(self, id: Optional[int] = None, ts: Optional[bytes] = None) -> int:
|
||||
"""Führt ein Update aus. Falls ID oder Timestamp nicht übergeben werden, wird
|
||||
@ -311,7 +301,7 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||
r = UseXmlRowUpdate(self.applus, self.table, id, ts=ts)
|
||||
for k, v in self.fields.items():
|
||||
r.addField(k, v)
|
||||
r.update();
|
||||
r.update()
|
||||
return id
|
||||
|
||||
def exec(self) -> int:
|
||||
@ -320,8 +310,8 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||
der DB existiert. In jedem Fall wird die ID des erzeugten oder geänderten Objekts geliefert.
|
||||
"""
|
||||
|
||||
id = self.checkExists();
|
||||
if id == None:
|
||||
id = self.checkExists()
|
||||
if id is None:
|
||||
return self.insert()
|
||||
else:
|
||||
return self.update(id=id)
|
||||
@ -332,4 +322,4 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||
Dies ist eine Umbenennung von :meth:`exec`.
|
||||
Es wird die ID des Eintrages geliefert
|
||||
"""
|
||||
return self.exec();
|
||||
return self.exec()
|
||||
|
@ -6,8 +6,6 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Dupliziert ein oder mehrere APplus Business-Objekte
|
||||
"""
|
||||
@ -19,9 +17,9 @@ from .applus import APplusServer
|
||||
import pyodbc # type: ignore
|
||||
import traceback
|
||||
import logging
|
||||
from typing import *
|
||||
from typing import List, Set, Optional, Dict, Tuple, Sequence, Any, Union
|
||||
|
||||
logger = logging.getLogger(__name__);
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
noCopyFields = sql_utils.normaliseDBfieldSet({"INSUSER", "UPDDATE", "TIMESTAMP", "MANDANT", "GUID", "ID", "TIMESTAMP_A", "INSDATE", "ID_A", "UPDUSER"})
|
||||
"""Menge von Feld-Namen, die nie kopiert werden sollen."""
|
||||
@ -38,7 +36,7 @@ def getFieldsToCopyForTable(server : APplusServer, table : str, force:bool=True)
|
||||
fields: Set[str]
|
||||
if (xmlDefs is None):
|
||||
if not force:
|
||||
raise Exception ("Keine XML-Definitionen für '{}' gefunden".format(table));
|
||||
raise Exception("Keine XML-Definitionen für '{}' gefunden".format(table))
|
||||
(fields, excl) = (set(), True)
|
||||
else:
|
||||
(fields, excl) = xmlDefs.getDuplicate()
|
||||
@ -46,8 +44,7 @@ def getFieldsToCopyForTable(server : APplusServer, table : str, force:bool=True)
|
||||
return fields.difference(noCopyFields)
|
||||
|
||||
allFields = server.getTableFields(table, isComputed=False)
|
||||
return allFields.difference(fields).difference(noCopyFields);
|
||||
|
||||
return allFields.difference(fields).difference(noCopyFields)
|
||||
|
||||
|
||||
class FieldsToCopyForTableCache():
|
||||
@ -85,7 +82,7 @@ def initFieldsToCopyForTableCacheIfNeeded(server : APplusServer, cache : Optiona
|
||||
if cache is None:
|
||||
return FieldsToCopyForTableCache(server)
|
||||
else:
|
||||
return cache;
|
||||
return cache
|
||||
|
||||
|
||||
class DuplicateBusinessObject():
|
||||
@ -176,9 +173,9 @@ class DuplicateBusinessObject():
|
||||
nonlocal res
|
||||
insertRow: applus_usexml.UseXmlRow
|
||||
if do.allowUpdate:
|
||||
insertRow = server.mkUseXMLRowInsertOrUpdate(do.table);
|
||||
insertRow = server.mkUseXMLRowInsertOrUpdate(do.table)
|
||||
else:
|
||||
insertRow = server.mkUseXMLRowInsert(do.table);
|
||||
insertRow = server.mkUseXMLRowInsert(do.table)
|
||||
|
||||
for f, v in do.fields.items():
|
||||
insertRow.addField(f, v)
|
||||
@ -188,7 +185,7 @@ class DuplicateBusinessObject():
|
||||
res.add(do.table, id)
|
||||
return id
|
||||
except:
|
||||
msg = traceback.format_exc();
|
||||
msg = traceback.format_exc()
|
||||
logger.error("Exception inserting BusinessObjekt: %s\n%s", str(insertRow), msg)
|
||||
return None
|
||||
|
||||
@ -209,7 +206,7 @@ class DuplicateBusinessObject():
|
||||
|
||||
# load missing fields from DB
|
||||
if len(connectMissing) > 0:
|
||||
sql = sql_utils.SqlStatementSelect(do.table);
|
||||
sql = sql_utils.SqlStatementSelect(do.table)
|
||||
sql.where.addConditionFieldEq("id", doID)
|
||||
for fd in connectMissing:
|
||||
sql.addFields(fd)
|
||||
@ -224,7 +221,6 @@ class DuplicateBusinessObject():
|
||||
if not (id is None):
|
||||
insertDeps(so, id)
|
||||
|
||||
|
||||
def insertDeps(do: 'DuplicateBusinessObject', doID: int) -> None:
|
||||
for so in do.dependentObjs:
|
||||
insertDep(do, doID, so["dependentObj"], so["connection"])
|
||||
@ -235,7 +231,6 @@ class DuplicateBusinessObject():
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def setFields(self, upds: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Setzt Felder des DuplicateBusinessObjektes und falls nötig seiner Unterobjekte.
|
||||
@ -257,14 +252,12 @@ class DuplicateBusinessObject():
|
||||
subupds[fs] = upds[fp]
|
||||
setFieldsInternal(su["dependentObj"], subupds)
|
||||
|
||||
|
||||
updsNorm: Dict[str, Any] = {}
|
||||
for f, v in upds.items():
|
||||
updsNorm[sql_utils.normaliseDBfield(f)] = v
|
||||
setFieldsInternal(self, updsNorm)
|
||||
|
||||
|
||||
|
||||
def _loadDBDuplicateBusinessObjectDict(
|
||||
server: APplusServer,
|
||||
table: str,
|
||||
@ -282,7 +275,7 @@ def _loadDBDuplicateBusinessObjectDict(
|
||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||
:return: das neue DuplicateBusinessObject
|
||||
"""
|
||||
table = table.upper();
|
||||
table = table.upper()
|
||||
|
||||
def getFieldsToCopy() -> Set[str]:
|
||||
if cache is None:
|
||||
@ -290,20 +283,18 @@ def _loadDBDuplicateBusinessObjectDict(
|
||||
else:
|
||||
return cache.getFieldsToCopyForTable(table)
|
||||
|
||||
|
||||
def getFields() -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||
ftc = getFieldsToCopy()
|
||||
fields = {}
|
||||
fieldsNotCopied = {}
|
||||
for f, v in applus_db.row_to_dict(row).items():
|
||||
f = sql_utils.normaliseDBfield(f);
|
||||
f = sql_utils.normaliseDBfield(f)
|
||||
if f in ftc:
|
||||
fields[f] = v
|
||||
else:
|
||||
fieldsNotCopied[f] = v
|
||||
return (fields, fieldsNotCopied)
|
||||
|
||||
|
||||
if (row is None):
|
||||
return None
|
||||
|
||||
@ -335,21 +326,22 @@ def loadDBDuplicateBusinessObject(
|
||||
:return: das neue DuplicateBusinessObject
|
||||
:rtype: Optional[DuplicateBusinessObject]
|
||||
"""
|
||||
table = table.upper();
|
||||
table = table.upper()
|
||||
|
||||
def getRow() -> pyodbc.Row:
|
||||
sql = sql_utils.SqlStatementSelect(table)
|
||||
sql.setTop(1)
|
||||
sql.where.addCondition(cond);
|
||||
sql.where.addCondition(cond)
|
||||
return server.dbQuerySingleRow(sql)
|
||||
|
||||
return _loadDBDuplicateBusinessObjectDict(server, table, getRow(), cache=cache, allowUpdate=allowUpdate);
|
||||
return _loadDBDuplicateBusinessObjectDict(server, table, getRow(), cache=cache, allowUpdate=allowUpdate)
|
||||
|
||||
|
||||
def loadDBDuplicateBusinessObjectSimpleCond(
|
||||
server: APplusServer,
|
||||
table: str,
|
||||
field: str,
|
||||
value : Optional[Union[sql_utils.SqlValue, bool]],
|
||||
value: Union[sql_utils.SqlValue, bool, None],
|
||||
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||
allowUpdate: bool = False) -> Optional[DuplicateBusinessObject]:
|
||||
"""
|
||||
@ -402,11 +394,12 @@ def loadDBDuplicateBusinessObjects(
|
||||
sql.where.addCondition(cond)
|
||||
return server.dbQueryAll(sql, apply=processRow)
|
||||
|
||||
|
||||
def loadDBDuplicateBusinessObjectsSimpleCond(
|
||||
server: APplusServer,
|
||||
table: str,
|
||||
field: str,
|
||||
value : Optional[Union[sql_utils.SqlValue, bool]],
|
||||
value: Union[sql_utils.SqlValue, bool, None],
|
||||
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||
allowUpdate: bool = False) -> Sequence[DuplicateBusinessObject]:
|
||||
"""
|
||||
@ -449,7 +442,7 @@ def loadDBDuplicateAPlan(
|
||||
:rtype: DuplicateBusinessObject
|
||||
"""
|
||||
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||
boMain = loadDBDuplicateBusinessObjectSimpleCond(server, "aplan", "APLAN", aplan, cache=cache)
|
||||
if boMain is None:
|
||||
return None
|
||||
@ -474,7 +467,7 @@ def loadDBDuplicateStueli(server : APplusServer, stueli : str, cache:Optional[Fi
|
||||
:rtype: Optional[DuplicateBusinessObject]
|
||||
"""
|
||||
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||
boMain = loadDBDuplicateBusinessObjectSimpleCond(server, "stueli", "stueli", stueli, cache=cache)
|
||||
if boMain is None:
|
||||
return None
|
||||
@ -484,6 +477,7 @@ def loadDBDuplicateStueli(server : APplusServer, stueli : str, cache:Optional[Fi
|
||||
|
||||
return boMain
|
||||
|
||||
|
||||
def addSachgruppeDependentObjects(
|
||||
do: DuplicateBusinessObject,
|
||||
server: APplusServer,
|
||||
@ -498,9 +492,9 @@ def addSachgruppeDependentObjects(
|
||||
:type cache: Optional[FieldsToCopyForTableCache]
|
||||
"""
|
||||
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||
klasse = do.fields.get(sql_utils.normaliseDBfield("SACHGRUPPENKLASSE"), None)
|
||||
if (klasse == None):
|
||||
if (klasse is None):
|
||||
# keine Klasse gesetzt, nichts zu kopieren
|
||||
return
|
||||
|
||||
@ -511,7 +505,7 @@ def addSachgruppeDependentObjects(
|
||||
sql.where.addConditionFieldEq("tabelle", do.table)
|
||||
return server.dbQueryAll(sql, apply=lambda r: r.sachgruppe)
|
||||
|
||||
gruppen = loadGruppen();
|
||||
gruppen = loadGruppen()
|
||||
|
||||
# Gruppe bearbeiten
|
||||
def processGruppen() -> None:
|
||||
@ -525,11 +519,9 @@ def addSachgruppeDependentObjects(
|
||||
for so in loadDBDuplicateBusinessObjects(server, "sachwert", cond, cache=cache, allowUpdate=True):
|
||||
do.addDependentBusinessObject(so, ("guid", "instanzguid"))
|
||||
|
||||
|
||||
processGruppen()
|
||||
|
||||
|
||||
|
||||
def loadDBDuplicateArtikel(
|
||||
server: APplusServer,
|
||||
artikel: str,
|
||||
@ -554,7 +546,7 @@ def loadDBDuplicateArtikel(
|
||||
:rtype: DuplicateBusinessObject
|
||||
"""
|
||||
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||
boArt = loadDBDuplicateBusinessObjectSimpleCond(server, "artikel", "ARTIKEL", artikel, cache=cache)
|
||||
if boArt is None:
|
||||
return None
|
||||
|
@ -8,14 +8,13 @@
|
||||
|
||||
"""Pandas Interface für PyAPplus64."""
|
||||
|
||||
from typing import Annotated as Ann
|
||||
import pandas as pd # type: ignore
|
||||
from pandas._typing import AggFuncType, FilePath, WriteExcelBuffer # type: ignore
|
||||
import sqlalchemy
|
||||
import traceback
|
||||
from .applus import APplusServer
|
||||
from .applus import sql_utils
|
||||
from typing import *
|
||||
from typing import Optional, Callable, Sequence, Tuple, Any, Union
|
||||
|
||||
|
||||
def createSqlAlchemyEngine(server: APplusServer) -> sqlalchemy.Engine:
|
||||
@ -36,32 +35,32 @@ def pandasReadSql(
|
||||
"""
|
||||
|
||||
if engine is None:
|
||||
engine = createSqlAlchemyEngine(server);
|
||||
engine = createSqlAlchemyEngine(server)
|
||||
with engine.connect() as conn:
|
||||
return pd.read_sql(sqlalchemy.text(server.completeSQL(sql, raw=raw)), conn)
|
||||
|
||||
|
||||
def _createHyperLinkGeneral(genOrg : Callable[[], str|int|float], genLink: Callable[[], str]) -> str|int|float:
|
||||
def _createHyperLinkGeneral(genOrg: Callable[[], Union[str, int, float]], genLink: Callable[[], str]) -> Union[str, int, float]:
|
||||
"""
|
||||
Hilfsfunktion zum Generieren eines Excel-Links.
|
||||
|
||||
:param genLink: Funktion, die Parameter aufgerufen wird und einen Link generiert
|
||||
"""
|
||||
org:str|int|float=""
|
||||
org2:str|int|float
|
||||
org: Union[str, int, float] = ""
|
||||
org2: Union[str, int, float]
|
||||
try:
|
||||
org = genOrg();
|
||||
org = genOrg()
|
||||
if not org:
|
||||
return org
|
||||
else:
|
||||
if isinstance(org, (int, float)):
|
||||
org2 = org;
|
||||
org2 = org
|
||||
else:
|
||||
org2 = "\"" + str(org).replace("\"", "\"\"") + "\""
|
||||
|
||||
return "=HYPERLINK(\"{}\", {})".format(genLink(), org2)
|
||||
except:
|
||||
msg = traceback.format_exc();
|
||||
msg = traceback.format_exc()
|
||||
print("Exception: {}".format(msg))
|
||||
return org
|
||||
|
||||
@ -78,14 +77,14 @@ def mkDataframeColumn(df : pd.DataFrame, makeValue : AggFuncType) -> pd.Series:
|
||||
try:
|
||||
return makeValue(r)
|
||||
except:
|
||||
msg = traceback.format_exc();
|
||||
msg = traceback.format_exc()
|
||||
print("Exception: {}".format(msg))
|
||||
return ""
|
||||
|
||||
if (len(df.index) > 0):
|
||||
return df.apply(mkValueWrapper, axis=1)
|
||||
else:
|
||||
return df.apply(lambda r: "", axis=1);
|
||||
return df.apply(lambda r: "", axis=1)
|
||||
|
||||
|
||||
def mkHyperlinkDataframeColumn(df: pd.DataFrame, makeOrig: AggFuncType, makeLink: Callable[[Any], str]) -> pd.Series:
|
||||
@ -100,11 +99,11 @@ def mkHyperlinkDataframeColumn(df : pd.DataFrame, makeOrig : AggFuncType, makeLi
|
||||
if (len(df.index) > 0):
|
||||
return df.apply(lambda r: _createHyperLinkGeneral(lambda: makeOrig(r), lambda: makeLink(r)), axis=1)
|
||||
else:
|
||||
return df.apply(lambda r: "", axis=1);
|
||||
return df.apply(lambda r: "", axis=1)
|
||||
|
||||
|
||||
def exportToExcel(
|
||||
filename:FilePath | WriteExcelBuffer | pd.ExcelWriter,
|
||||
filename: Union[FilePath, WriteExcelBuffer, pd.ExcelWriter],
|
||||
dfs: Sequence[Tuple[pd.DataFrame, str]],
|
||||
addTable: bool = True) -> None:
|
||||
"""
|
||||
@ -126,6 +125,4 @@ def exportToExcel(
|
||||
ws.add_table(0, 0, max_row, max_col - 1, {'columns': column_settings})
|
||||
|
||||
# Spaltenbreiten anpassen
|
||||
ws.autofit();
|
||||
|
||||
|
||||
ws.autofit()
|
||||
|
@ -6,7 +6,6 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
"""
|
||||
Diese Datei enthält Funktionen für den Bau von SQL Statements, besonders
|
||||
SELECT-Statements. Es gibt viel ausgefeiltere Methoden für die Erstellung von
|
||||
@ -22,19 +21,22 @@ APplus. Oft ist es sinnvoll, solche Parameter zu verwenden.
|
||||
|
||||
from __future__ import annotations
|
||||
import datetime
|
||||
from typing import *
|
||||
from typing import Set, Sequence, Union, Optional, cast, List
|
||||
|
||||
|
||||
def normaliseDBfield(f: str) -> str:
|
||||
"""Normalisiert die Darstellung eines DB-Feldes"""
|
||||
return str(f).upper();
|
||||
return str(f).upper()
|
||||
|
||||
|
||||
def normaliseDBfieldSet(s: Set[str]) -> Set[str]:
|
||||
"""Normalisiert eine Menge von DB-Feldern"""
|
||||
return {normaliseDBfield(f) for f in s}
|
||||
|
||||
def normaliseDBfieldList(l : Sequence[str]) -> Sequence[str]:
|
||||
|
||||
def normaliseDBfieldList(fields: Sequence[str]) -> Sequence[str]:
|
||||
"""Normalisiert eine Menge von DB-Feldern"""
|
||||
return [normaliseDBfield(f) for f in l]
|
||||
return [normaliseDBfield(f) for f in fields]
|
||||
|
||||
|
||||
class SqlField():
|
||||
@ -45,10 +47,11 @@ class SqlField():
|
||||
:type fn: str
|
||||
"""
|
||||
def __init__(self, fn: str):
|
||||
self.field = normaliseDBfield(fn);
|
||||
self.field = normaliseDBfield(fn)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.field;
|
||||
return self.field
|
||||
|
||||
|
||||
class SqlFixed():
|
||||
"""
|
||||
@ -58,10 +61,11 @@ class SqlFixed():
|
||||
:type s: str
|
||||
"""
|
||||
def __init__(self, s: str):
|
||||
self.s = str(s);
|
||||
self.s = str(s)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.s;
|
||||
return self.s
|
||||
|
||||
|
||||
class SqlDateTime():
|
||||
"""
|
||||
@ -71,13 +75,14 @@ class SqlDateTime():
|
||||
:type dt: Union[datetime.datetime, datetime.date]
|
||||
"""
|
||||
def __init__(self, dt: Union[datetime.datetime, datetime.date] = datetime.datetime.now()) -> None:
|
||||
self.value = dt;
|
||||
self.value = dt
|
||||
|
||||
def __str__(self) -> str:
|
||||
# %f formatiert mit 6 Stellen, also microseconds. Es werden aber nur
|
||||
# 3 Stellen unterstützt, daher werden 3 weggeworfen.
|
||||
return self.value.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
|
||||
|
||||
|
||||
class SqlDate():
|
||||
"""
|
||||
Wrapper um DateTime, die die Formatierung erleichtern
|
||||
@ -86,10 +91,11 @@ class SqlDate():
|
||||
:type d: Union[datetime.datetime, datetime.date]
|
||||
"""
|
||||
def __init__(self, d: Union[datetime.datetime, datetime.date] = datetime.datetime.now()) -> None:
|
||||
self.value = d;
|
||||
self.value = d
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value.strftime("%Y%m%d");
|
||||
return self.value.strftime("%Y%m%d")
|
||||
|
||||
|
||||
class SqlTime():
|
||||
"""
|
||||
@ -104,6 +110,7 @@ class SqlTime():
|
||||
def __str__(self) -> str:
|
||||
return self.value.strftime("%H:%M:%S.%f")[:-3]
|
||||
|
||||
|
||||
class SqlParam():
|
||||
"""Hilfsklasse, für einen Parameter (?)"""
|
||||
def __init__(self) -> None:
|
||||
@ -112,9 +119,11 @@ class SqlParam():
|
||||
def __str__(self) -> str:
|
||||
return "?"
|
||||
|
||||
|
||||
sqlParam = SqlParam()
|
||||
"""Da SqlParam keinen Zustand hat, reicht ein einzelner statischer Wert"""
|
||||
|
||||
|
||||
def formatSqlValueString(s: str) -> str:
|
||||
"""
|
||||
Formatiert einen String für ein Sql-Statement. Der String wird in "'" eingeschlossen
|
||||
@ -127,14 +136,15 @@ def formatSqlValueString(s:str) -> str:
|
||||
"""
|
||||
|
||||
if (s is None):
|
||||
return "''";
|
||||
return "''"
|
||||
|
||||
return "'" + str(s).replace("'", "''") + "'";
|
||||
return "'" + str(s).replace("'", "''") + "'"
|
||||
|
||||
|
||||
SqlValue : TypeAlias = Union[str, int, float, SqlParam, SqlField, SqlFixed, SqlDate, SqlDateTime, datetime.datetime, datetime.date, datetime.time]
|
||||
SqlValue = Union[str, int, float, SqlParam, SqlField, SqlFixed, SqlDate, SqlDateTime, datetime.datetime, datetime.date, datetime.time]
|
||||
"""Union-Type aller unterstützter SQL-Werte"""
|
||||
|
||||
|
||||
def formatSqlValue(v: SqlValue) -> str:
|
||||
"""
|
||||
Formatiert einen Wert für SQL. Je nachdem um welchen Typ es sich handelt, werden andere Formatierungen verwendet.
|
||||
@ -145,25 +155,26 @@ def formatSqlValue(v : SqlValue) -> str:
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
if (v == None):
|
||||
raise Exception("formatSqlValue: null not supported");
|
||||
if v is None:
|
||||
raise Exception("formatSqlValue: null not supported")
|
||||
|
||||
if isinstance(v, (int, float, SqlField)):
|
||||
return str(v);
|
||||
return str(v)
|
||||
elif isinstance(v, str):
|
||||
return formatSqlValueString(v);
|
||||
return formatSqlValueString(v)
|
||||
elif isinstance(v, datetime.datetime):
|
||||
return "'" + str(SqlDateTime(v)) + "'";
|
||||
return "'" + str(SqlDateTime(v)) + "'"
|
||||
elif isinstance(v, datetime.date):
|
||||
return "'" + str(SqlDate(v)) + "'";
|
||||
return "'" + str(SqlDate(v)) + "'"
|
||||
elif isinstance(v, datetime.time):
|
||||
return "'" + str(SqlTime(v)) + "'";
|
||||
return "'" + str(SqlTime(v)) + "'"
|
||||
elif isinstance(v, (SqlDateTime, SqlDate, SqlTime)):
|
||||
return "'" + str(v) + "'";
|
||||
return "'" + str(v) + "'"
|
||||
elif isinstance(v, (SqlParam, SqlFixed)):
|
||||
return str(v);
|
||||
return str(v)
|
||||
else:
|
||||
raise Exception("formatSqlValue: unsupported type {}".format(type(v)));
|
||||
raise Exception("formatSqlValue: unsupported type {}".format(type(v)))
|
||||
|
||||
|
||||
class SqlCondition():
|
||||
"""Eine abstrakte Sql-Bedingung. Unterklassen erledigen die eigentliche Arbeit."""
|
||||
@ -175,20 +186,21 @@ class SqlCondition():
|
||||
:return: die Bedingung
|
||||
:rtype: str
|
||||
"""
|
||||
raise Exception("Not implemented");
|
||||
raise Exception("Not implemented")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.getCondition();
|
||||
return self.getCondition()
|
||||
|
||||
|
||||
class SqlConditionPrepared(SqlCondition):
|
||||
"""Eine einfache Sql-Bedingung, die immer einen festen String zurückgibt."""
|
||||
|
||||
def __init__(self, cond: Union[SqlCondition, str]):
|
||||
self.cond = str(cond);
|
||||
self.cond = str(cond)
|
||||
|
||||
def getCondition(self) -> str:
|
||||
return self.cond;
|
||||
return self.cond
|
||||
|
||||
|
||||
class SqlConditionTrue(SqlConditionPrepared):
|
||||
"""True-Bedingung"""
|
||||
@ -196,12 +208,14 @@ class SqlConditionTrue(SqlConditionPrepared):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("(1=1)")
|
||||
|
||||
|
||||
class SqlConditionFalse(SqlConditionPrepared):
|
||||
"""False-Bedingung"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("(1=0)")
|
||||
|
||||
|
||||
class SqlConditionBool(SqlConditionPrepared):
|
||||
"""Fixe True-oder-False Bedingung"""
|
||||
|
||||
@ -211,6 +225,7 @@ class SqlConditionBool(SqlConditionPrepared):
|
||||
else:
|
||||
super().__init__(SqlConditionFalse())
|
||||
|
||||
|
||||
class SqlConditionNot(SqlCondition):
|
||||
"""
|
||||
Negation einer anderen Bedingung
|
||||
@ -220,10 +235,10 @@ class SqlConditionNot(SqlCondition):
|
||||
"""
|
||||
|
||||
def __init__(self, cond: SqlCondition):
|
||||
self.cond = cond;
|
||||
self.cond = cond
|
||||
|
||||
def getCondition(self) -> str:
|
||||
return "(not {})".format(self.cond.getCondition());
|
||||
return "(not {})".format(self.cond.getCondition())
|
||||
|
||||
|
||||
class SqlConditionIsNull(SqlConditionPrepared):
|
||||
@ -237,10 +252,12 @@ class SqlConditionIsNull(SqlConditionPrepared):
|
||||
def __init__(self, v: SqlValue):
|
||||
super().__init__("({} is null)".format(formatSqlValue(v)))
|
||||
|
||||
|
||||
class SqlConditionFieldIsNull(SqlConditionIsNull):
|
||||
def __init__(self, field: str):
|
||||
super().__init__(SqlField(field))
|
||||
|
||||
|
||||
class SqlConditionIsNotNull(SqlConditionPrepared):
|
||||
"""
|
||||
Wert soll nicht null sein
|
||||
@ -252,10 +269,12 @@ class SqlConditionIsNotNull(SqlConditionPrepared):
|
||||
def __init__(self, v: SqlValue):
|
||||
super().__init__("({} is not null)".format(formatSqlValue(v)))
|
||||
|
||||
|
||||
class SqlConditionFieldIsNotNull(SqlConditionIsNotNull):
|
||||
def __init__(self, field: str):
|
||||
super().__init__(SqlField(field))
|
||||
|
||||
|
||||
class SqlConditionStringStartsWith(SqlConditionPrepared):
|
||||
"""
|
||||
Feld soll mit einem bestimmten String beginnen
|
||||
@ -267,9 +286,9 @@ class SqlConditionStringStartsWith(SqlConditionPrepared):
|
||||
"""
|
||||
|
||||
def __init__(self, field: str, value: str):
|
||||
cond = "";
|
||||
cond = ""
|
||||
if value:
|
||||
cond="(left({}, {}) = {})".format(normaliseDBfield(field), len(value), formatSqlValueString(value));
|
||||
cond = "(left({}, {}) = {})".format(normaliseDBfield(field), len(value), formatSqlValueString(value))
|
||||
else:
|
||||
cond = "(1=1)"
|
||||
super().__init__(cond)
|
||||
@ -285,8 +304,8 @@ class SqlConditionFieldStringNotEmpty(SqlConditionPrepared):
|
||||
"""
|
||||
|
||||
def __init__(self, field: str):
|
||||
field = normaliseDBfield(field);
|
||||
cond="({} is not null and {} != '')".format(field, field);
|
||||
field = normaliseDBfield(field)
|
||||
cond = "({} is not null and {} != '')".format(field, field)
|
||||
super().__init__(cond)
|
||||
|
||||
|
||||
@ -312,6 +331,7 @@ class SqlConditionIn(SqlConditionPrepared):
|
||||
cond = "({} in ({}))".format(formatSqlValue(value), valuesS)
|
||||
super().__init__(cond)
|
||||
|
||||
|
||||
class SqlConditionFieldIn(SqlConditionIn):
|
||||
def __init__(self, field: str, values: Sequence[SqlValue]):
|
||||
super().__init__(SqlField(field), values)
|
||||
@ -324,7 +344,7 @@ class SqlConditionEq(SqlConditionPrepared):
|
||||
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
||||
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
||||
"""
|
||||
def __init__(self, value1 : Optional[Union[SqlValue, bool]], value2 : Optional[Union[SqlValue, bool]]):
|
||||
def __init__(self, value1: Union[SqlValue, bool, None], value2: Union[SqlValue, bool, None]):
|
||||
cond: Union[SqlCondition, str]
|
||||
if (value1 is None) and (value2 is None):
|
||||
cond = SqlConditionTrue()
|
||||
@ -340,13 +360,13 @@ class SqlConditionEq(SqlConditionPrepared):
|
||||
cond = SqlConditionIsNull(value1)
|
||||
else:
|
||||
if isinstance(value1, bool) and isinstance(value2, bool):
|
||||
cond = SqlConditionBool(value1 == value2);
|
||||
cond = SqlConditionBool(value1 == value2)
|
||||
elif isinstance(value1, bool) and not isinstance(value2, bool):
|
||||
value2 = cast(SqlValue, value2)
|
||||
if value1:
|
||||
cond = "({} = 1)".format(formatSqlValue(value2))
|
||||
else:
|
||||
cond = "({} = 0 OR {} is null)".format(formatSqlValue(value2), formatSqlValue(value2));
|
||||
cond = "({} = 0 OR {} is null)".format(formatSqlValue(value2), formatSqlValue(value2))
|
||||
elif not isinstance(value1, bool) and isinstance(value2, bool):
|
||||
value1 = cast(SqlValue, value1)
|
||||
if value2:
|
||||
@ -356,7 +376,7 @@ class SqlConditionEq(SqlConditionPrepared):
|
||||
else:
|
||||
value1 = cast(SqlValue, value1)
|
||||
value2 = cast(SqlValue, value2)
|
||||
cond = "({} = {})".format(formatSqlValue(value1), formatSqlValue(value2));
|
||||
cond = "({} = {})".format(formatSqlValue(value1), formatSqlValue(value2))
|
||||
super().__init__(cond)
|
||||
|
||||
|
||||
@ -372,10 +392,10 @@ class SqlConditionBinComp(SqlConditionPrepared):
|
||||
:type value2: SqlValue
|
||||
"""
|
||||
def __init__(self, op: str, value1: SqlValue, value2: SqlValue):
|
||||
if not(value1) or not(value2):
|
||||
if not value1 or not value2:
|
||||
raise Exception("SqlConditionBinComp: value not provided")
|
||||
|
||||
cond = "({} {} {})".format(formatSqlValue(value1), op, formatSqlValue(value2));
|
||||
cond = "({} {} {})".format(formatSqlValue(value1), op, formatSqlValue(value2))
|
||||
super().__init__(cond)
|
||||
|
||||
|
||||
@ -389,6 +409,7 @@ class SqlConditionLt(SqlConditionBinComp):
|
||||
def __init__(self, value1: SqlValue, value2: SqlValue):
|
||||
super().__init__("<", value1, value2)
|
||||
|
||||
|
||||
class SqlConditionLe(SqlConditionBinComp):
|
||||
"""
|
||||
Bedingung der Form 'value1 <= value2'
|
||||
@ -399,6 +420,7 @@ class SqlConditionLe(SqlConditionBinComp):
|
||||
def __init__(self, value1: SqlValue, value2: SqlValue):
|
||||
super().__init__("<=", value1, value2)
|
||||
|
||||
|
||||
class SqlConditionGt(SqlConditionBinComp):
|
||||
"""
|
||||
Bedingung der Form 'value1 > value2'
|
||||
@ -409,6 +431,7 @@ class SqlConditionGt(SqlConditionBinComp):
|
||||
def __init__(self, value1: SqlValue, value2: SqlValue):
|
||||
super().__init__(">", value1, value2)
|
||||
|
||||
|
||||
class SqlConditionGe(SqlConditionBinComp):
|
||||
"""
|
||||
Bedingung der Form 'value1 >= value2'
|
||||
@ -421,25 +444,30 @@ class SqlConditionGe(SqlConditionBinComp):
|
||||
|
||||
|
||||
class SqlConditionFieldEq(SqlConditionEq):
|
||||
def __init__(self, field : str, value : Optional[Union[SqlValue, bool]]):
|
||||
def __init__(self, field: str, value: Union[SqlValue, bool, None]):
|
||||
super().__init__(SqlField(field), value)
|
||||
|
||||
|
||||
class SqlConditionFieldLt(SqlConditionLt):
|
||||
def __init__(self, field: str, value: SqlValue):
|
||||
super().__init__(SqlField(field), value)
|
||||
|
||||
|
||||
class SqlConditionFieldLe(SqlConditionLe):
|
||||
def __init__(self, field: str, value: SqlValue):
|
||||
super().__init__(SqlField(field), value)
|
||||
|
||||
|
||||
class SqlConditionFieldGt(SqlConditionGt):
|
||||
def __init__(self, field: str, value: SqlValue):
|
||||
super().__init__(SqlField(field), value)
|
||||
|
||||
|
||||
class SqlConditionFieldGe(SqlConditionGe):
|
||||
def __init__(self, field: str, value: SqlValue):
|
||||
super().__init__(SqlField(field), value)
|
||||
|
||||
|
||||
class SqlConditionList(SqlCondition):
|
||||
"""
|
||||
Eine SQL Bedingung, die sich aus einer Liste anderer Bedingungen zusammensetzen.
|
||||
@ -452,93 +480,93 @@ class SqlConditionList(SqlCondition):
|
||||
"""
|
||||
|
||||
def __init__(self, connector: str, emptyCond: str):
|
||||
self.connector : str = connector;
|
||||
self.emptyCond : str = emptyCond;
|
||||
self.connector: str = connector
|
||||
self.emptyCond: str = emptyCond
|
||||
self.elems: List[SqlCondition] = []
|
||||
|
||||
def addCondition(self, cond : SqlCondition | str | None) -> None:
|
||||
def addCondition(self, cond: Union[SqlCondition, str, None]) -> None:
|
||||
if (cond is None):
|
||||
return
|
||||
if not (isinstance(cond, SqlCondition)):
|
||||
cond = SqlConditionPrepared("("+str(cond)+")");
|
||||
self.elems.append(cond);
|
||||
cond = SqlConditionPrepared("("+str(cond)+")")
|
||||
self.elems.append(cond)
|
||||
|
||||
def addConditions(self, *conds : SqlCondition | str | None) -> None:
|
||||
def addConditions(self, *conds: Union[SqlCondition, str, None]) -> None:
|
||||
for cond in conds:
|
||||
self.addCondition(cond)
|
||||
|
||||
def addConditionFieldStringNotEmpty(self, field: str) -> None:
|
||||
self.addCondition(SqlConditionFieldStringNotEmpty(field));
|
||||
self.addCondition(SqlConditionFieldStringNotEmpty(field))
|
||||
|
||||
def addConditionFieldIn(self, field: str, values: Sequence[SqlValue]) -> None:
|
||||
self.addCondition(SqlConditionFieldIn(field, values));
|
||||
self.addCondition(SqlConditionFieldIn(field, values))
|
||||
|
||||
def addConditionFieldEq(self, field : str, value : Optional[Union[SqlValue, bool]]) -> None:
|
||||
self.addCondition(SqlConditionFieldEq(field, value));
|
||||
def addConditionFieldEq(self, field: str, value: Union[SqlValue, bool, None]) -> None:
|
||||
self.addCondition(SqlConditionFieldEq(field, value))
|
||||
|
||||
def addConditionFieldsEq(self, field1: str, field2: str) -> None:
|
||||
self.addCondition(SqlConditionEq(SqlField(field1), SqlField(field2)));
|
||||
self.addCondition(SqlConditionEq(SqlField(field1), SqlField(field2)))
|
||||
|
||||
def addConditionEq(self, value1 : Optional[Union[SqlValue, bool]], value2 : Optional[Union[SqlValue, bool]]) -> None:
|
||||
self.addCondition(SqlConditionEq(value1, value2));
|
||||
def addConditionEq(self, value1: Union[SqlValue, bool, None], value2: Union[SqlValue, bool, None]) -> None:
|
||||
self.addCondition(SqlConditionEq(value1, value2))
|
||||
|
||||
def addConditionGe(self, value1: SqlValue, value2: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionGe(value1, value2));
|
||||
self.addCondition(SqlConditionGe(value1, value2))
|
||||
|
||||
def addConditionFieldGe(self, field: str, value: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionGe(SqlField(field), value));
|
||||
self.addCondition(SqlConditionGe(SqlField(field), value))
|
||||
|
||||
def addConditionFieldsGe(self, field1: str, field2: str) -> None:
|
||||
self.addCondition(SqlConditionGe(SqlField(field1), SqlField(field2)));
|
||||
self.addCondition(SqlConditionGe(SqlField(field1), SqlField(field2)))
|
||||
|
||||
def addConditionLe(self, value1: SqlValue, value2: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionLe(value1, value2));
|
||||
self.addCondition(SqlConditionLe(value1, value2))
|
||||
|
||||
def addConditionFieldLe(self, field: str, value: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionLe(SqlField(field), value));
|
||||
self.addCondition(SqlConditionLe(SqlField(field), value))
|
||||
|
||||
def addConditionFieldsLe(self, field1: str, field2: str) -> None:
|
||||
self.addCondition(SqlConditionLe(SqlField(field1), SqlField(field2)));
|
||||
self.addCondition(SqlConditionLe(SqlField(field1), SqlField(field2)))
|
||||
|
||||
def addConditionGt(self, value1: SqlValue, value2: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionGt(value1, value2));
|
||||
self.addCondition(SqlConditionGt(value1, value2))
|
||||
|
||||
def addConditionFieldGt(self, field: str, value: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionGt(SqlField(field), value));
|
||||
self.addCondition(SqlConditionGt(SqlField(field), value))
|
||||
|
||||
def addConditionFieldsGt(self, field1: str, field2: str) -> None:
|
||||
self.addCondition(SqlConditionGt(SqlField(field1), SqlField(field2)));
|
||||
self.addCondition(SqlConditionGt(SqlField(field1), SqlField(field2)))
|
||||
|
||||
def addConditionLt(self, value1: SqlValue, value2: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionLt(value1, value2));
|
||||
self.addCondition(SqlConditionLt(value1, value2))
|
||||
|
||||
def addConditionFieldLt(self, field: str, value: SqlValue) -> None:
|
||||
self.addCondition(SqlConditionLt(SqlField(field), value));
|
||||
self.addCondition(SqlConditionLt(SqlField(field), value))
|
||||
|
||||
def addConditionFieldsLt(self, field1: str, field2: str) -> None:
|
||||
self.addCondition(SqlConditionLt(SqlField(field1), SqlField(field2)));
|
||||
self.addCondition(SqlConditionLt(SqlField(field1), SqlField(field2)))
|
||||
|
||||
def addConditionFieldIsNull(self, field: str) -> None:
|
||||
self.addCondition(SqlConditionFieldIsNull(field));
|
||||
self.addCondition(SqlConditionFieldIsNull(field))
|
||||
|
||||
def addConditionFieldIsNotNull(self, field: str) -> None:
|
||||
self.addCondition(SqlConditionFieldIsNotNull(field));
|
||||
self.addCondition(SqlConditionFieldIsNotNull(field))
|
||||
|
||||
def isEmpty(self) -> bool:
|
||||
return not(self.elems);
|
||||
return not self.elems
|
||||
|
||||
def getCondition(self) -> str:
|
||||
match (len(self.elems)):
|
||||
case 0:
|
||||
return self.emptyCond;
|
||||
case 1:
|
||||
return self.elems[0].getCondition();
|
||||
case l:
|
||||
res = "(" + self.elems[0].getCondition();
|
||||
for i in range(1, l):
|
||||
elemLen = len(self.elems)
|
||||
if elemLen == 0:
|
||||
return self.emptyCond
|
||||
elif elemLen == 1:
|
||||
return self.elems[0].getCondition()
|
||||
else:
|
||||
res = "(" + self.elems[0].getCondition()
|
||||
for i in range(1, elemLen):
|
||||
res += " {} {}".format(self.connector, self.elems[i].getCondition())
|
||||
res += ")";
|
||||
return res;
|
||||
res += ")"
|
||||
return res
|
||||
|
||||
|
||||
class SqlConditionDateTimeFieldInRange(SqlConditionPrepared):
|
||||
@ -550,7 +578,7 @@ class SqlConditionDateTimeFieldInRange(SqlConditionPrepared):
|
||||
:param datetimeVon: der untere Wert (einschließlich), None erlaubt beliebige Zeiten
|
||||
:param datetimeBis: der obere Wert (ausschließlich), None erlaubt beliebige Zeiten
|
||||
"""
|
||||
def __init__(self, field : str, datetimeVon : datetime.datetime|None, datetimeBis : datetime.datetime|None):
|
||||
def __init__(self, field: str, datetimeVon: Optional[datetime.datetime], datetimeBis: Optional[datetime.datetime]):
|
||||
cond = SqlConditionAnd()
|
||||
if not (datetimeVon is None):
|
||||
cond.addConditionFieldGe(field, datetimeVon)
|
||||
@ -558,6 +586,7 @@ class SqlConditionDateTimeFieldInRange(SqlConditionPrepared):
|
||||
cond.addConditionFieldLt(field, datetimeBis)
|
||||
super().__init__(str(cond))
|
||||
|
||||
|
||||
class SqlConditionDateTimeFieldInMonth(SqlConditionPrepared):
|
||||
"""
|
||||
Liegt Datetime in einem bestimmten Monat?
|
||||
@ -570,15 +599,17 @@ class SqlConditionDateTimeFieldInMonth(SqlConditionPrepared):
|
||||
def __init__(self, field: str, year: int, month: int):
|
||||
if month == 12:
|
||||
nyear = year+1
|
||||
nmonth=1;
|
||||
nmonth = 1
|
||||
else:
|
||||
nyear = year
|
||||
nmonth=month+1;
|
||||
cond = SqlConditionDateTimeFieldInRange(field,
|
||||
nmonth = month+1
|
||||
cond = SqlConditionDateTimeFieldInRange(
|
||||
field,
|
||||
datetime.datetime(year=year, month=month, day=1),
|
||||
datetime.datetime(year=nyear, month=nmonth, day=1))
|
||||
super().__init__(str(cond))
|
||||
|
||||
|
||||
class SqlConditionDateTimeFieldInYear(SqlConditionPrepared):
|
||||
"""
|
||||
Liegt Datetime in einem bestimmten Jahr?
|
||||
@ -589,11 +620,13 @@ class SqlConditionDateTimeFieldInYear(SqlConditionPrepared):
|
||||
"""
|
||||
def __init__(self, field: str, year: int) -> None:
|
||||
nyear = year+1
|
||||
cond = SqlConditionDateTimeFieldInRange(field,
|
||||
cond = SqlConditionDateTimeFieldInRange(
|
||||
field,
|
||||
datetime.datetime(year=year, month=1, day=1),
|
||||
datetime.datetime(year=nyear, month=1, day=1))
|
||||
super().__init__(str(cond))
|
||||
|
||||
|
||||
class SqlConditionDateTimeFieldInDay(SqlConditionPrepared):
|
||||
"""
|
||||
Liegt Datetime in einem bestimmten Monat?
|
||||
@ -606,16 +639,19 @@ class SqlConditionDateTimeFieldInDay(SqlConditionPrepared):
|
||||
"""
|
||||
def __init__(self, field: str, year: int, month: int, day: int) -> None:
|
||||
d = datetime.datetime(year=year, month=month, day=day)
|
||||
cond = SqlConditionDateTimeFieldInRange(field,
|
||||
cond = SqlConditionDateTimeFieldInRange(
|
||||
field,
|
||||
d,
|
||||
d + datetime.timedelta(days=1))
|
||||
super().__init__(str(cond))
|
||||
|
||||
|
||||
class SqlConditionAnd(SqlConditionList):
|
||||
def __init__(self, *conds: Union[SqlCondition, str]) -> None:
|
||||
super().__init__("AND", "(1=1)")
|
||||
self.addConditions(*conds)
|
||||
|
||||
|
||||
class SqlConditionOr(SqlConditionList):
|
||||
def __init__(self, *conds: Union[SqlCondition, str]) -> None:
|
||||
super().__init__("OR", "(1=0)")
|
||||
@ -645,10 +681,10 @@ class SqlJoin():
|
||||
"""
|
||||
Liefert den Join als String
|
||||
"""
|
||||
return self.joinType + " " + self.table + " ON " + self.on.getCondition();
|
||||
return self.joinType + " " + self.table + " ON " + self.on.getCondition()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.getJoin();
|
||||
return self.getJoin()
|
||||
|
||||
|
||||
class SqlInnerJoin(SqlJoin):
|
||||
@ -663,6 +699,7 @@ class SqlInnerJoin(SqlJoin):
|
||||
def __init__(self, table: str, *conds: Union[SqlCondition, str]) -> None:
|
||||
super().__init__("INNER JOIN", table, *conds)
|
||||
|
||||
|
||||
class SqlLeftJoin(SqlJoin):
|
||||
"""
|
||||
Ein Left-Join.
|
||||
@ -674,6 +711,7 @@ class SqlLeftJoin(SqlJoin):
|
||||
def __init__(self, table: str, *conds: Union[SqlCondition, str]) -> None:
|
||||
super().__init__("LEFT JOIN", table, *conds)
|
||||
|
||||
|
||||
class SqlStatementSelect():
|
||||
"""
|
||||
Klasse, um einfache Select-Statements zu bauen.
|
||||
@ -690,38 +728,38 @@ class SqlStatementSelect():
|
||||
self.top: int = 0
|
||||
"""wie viele Datensätze auswählen? 0 für alle"""
|
||||
|
||||
self.where : SqlConditionList = SqlConditionAnd();
|
||||
self.where: SqlConditionList = SqlConditionAnd()
|
||||
"""die Bedingung, Default ist True"""
|
||||
|
||||
self.fields: List[str] = []
|
||||
"""Liste von auszuwählenden Feldern"""
|
||||
self.addFields(*fields)
|
||||
|
||||
self.joins : List[SqlJoin|str] = []
|
||||
self.joins: List[Union[SqlJoin, str]] = []
|
||||
"""Joins mit extra Tabellen"""
|
||||
|
||||
self.groupBy : List[str] = [];
|
||||
self.groupBy: List[str] = []
|
||||
"""die Bedingung, Default ist True"""
|
||||
|
||||
self.having : SqlConditionList = SqlConditionAnd();
|
||||
self.having: SqlConditionList = SqlConditionAnd()
|
||||
"""die Bedingung having, Default ist True"""
|
||||
|
||||
self.order: Optional[str] = None
|
||||
"""Sortierung"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.getSql();
|
||||
return self.getSql()
|
||||
|
||||
def addFields(self, *fields: str) -> None:
|
||||
"""Fügt ein oder mehrere Felder, also auszuwählende Werte zu einem SQL-Statement hinzu."""
|
||||
for f in fields:
|
||||
if not (f == None):
|
||||
if not (f is None):
|
||||
self.fields.append(f)
|
||||
|
||||
def addGroupBy(self, *fields: str) -> None:
|
||||
"""Fügt ein oder mehrere GroupBy Felder zu einem SQL-Statement hinzu."""
|
||||
for f in fields:
|
||||
if not (f == None):
|
||||
if not (f is None):
|
||||
self.groupBy.append(f)
|
||||
|
||||
def setTop(self, t: int) -> None:
|
||||
@ -735,10 +773,10 @@ class SqlStatementSelect():
|
||||
Dies kann im Vergleich zu 'addFields' Schreibarbeit erleitern.
|
||||
"""
|
||||
for f in fields:
|
||||
if not (f == None):
|
||||
if not (f is None):
|
||||
self.fields.append(table + "." + str(f))
|
||||
|
||||
def addJoin(self, j : SqlJoin|str) -> None:
|
||||
def addJoin(self, j: Union[SqlJoin, str]) -> None:
|
||||
"""Fügt ein Join zum SQL-Statement hinzu. Beispiel: 'LEFT JOIN personal p ON t.UPDUSER = p.PERSONAL'"""
|
||||
self.joins.append(j)
|
||||
|
||||
@ -755,36 +793,35 @@ class SqlStatementSelect():
|
||||
def getSql(self) -> str:
|
||||
"""Liefert das SQL-SELECT-Statement als String"""
|
||||
def getFields() -> str:
|
||||
match (len(self.fields)):
|
||||
case 0:
|
||||
return "*";
|
||||
case 1:
|
||||
return str(self.fields[0]);
|
||||
case l:
|
||||
res = str(self.fields[0]);
|
||||
for i in range(1, l):
|
||||
res += ", " + str(self.fields[i]);
|
||||
return res;
|
||||
fieldsLen = len(self.fields)
|
||||
if fieldsLen == 0:
|
||||
return "*"
|
||||
elif fieldsLen == 1:
|
||||
return str(self.fields[0])
|
||||
else:
|
||||
res = str(self.fields[0])
|
||||
for i in range(1, fieldsLen):
|
||||
res += ", " + str(self.fields[i])
|
||||
return res
|
||||
|
||||
def getGroupBy() -> str:
|
||||
match (len(self.groupBy)):
|
||||
case 0:
|
||||
return "";
|
||||
case l:
|
||||
groupByLen = len(self.groupBy)
|
||||
if groupByLen == 0:
|
||||
return ""
|
||||
else:
|
||||
res = " GROUP BY " + str(self.fields[0])
|
||||
for i in range(1, l):
|
||||
res += ", " + str(self.fields[i]);
|
||||
for i in range(1, groupByLen):
|
||||
res += ", " + str(self.fields[i])
|
||||
if not (self.having.isEmpty()):
|
||||
res += " HAVING " + str(self.having)
|
||||
return res;
|
||||
return res
|
||||
|
||||
def getJoins() -> str:
|
||||
match (len(self.joins)):
|
||||
case 0:
|
||||
if (len(self.joins) == 0):
|
||||
return ""
|
||||
case l:
|
||||
res = "";
|
||||
for i in range(0, l):
|
||||
else:
|
||||
res = ""
|
||||
for i in range(0, len(self.joins)):
|
||||
res += " " + str(self.joins[i])
|
||||
return res
|
||||
|
||||
@ -795,7 +832,7 @@ class SqlStatementSelect():
|
||||
return " WHERE " + str(self.where)
|
||||
|
||||
def getOrder() -> str:
|
||||
if self.order == None:
|
||||
if self.order is None:
|
||||
return ""
|
||||
else:
|
||||
return " ORDER BY " + str(self.order)
|
||||
@ -806,8 +843,7 @@ class SqlStatementSelect():
|
||||
else:
|
||||
return "TOP " + str(self.top) + " "
|
||||
|
||||
return "SELECT " + getTop() + getFields() + " FROM " + self.table + getJoins() + getWhere() + getGroupBy() + getOrder();
|
||||
return "SELECT " + getTop() + getFields() + " FROM " + self.table + getJoins() + getWhere() + getGroupBy() + getOrder()
|
||||
|
||||
|
||||
SqlStatement : TypeAlias = Union [SqlStatementSelect, str]
|
||||
|
||||
SqlStatement = Union[SqlStatementSelect, str]
|
||||
|
@ -6,11 +6,10 @@
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
import pathlib
|
||||
import datetime
|
||||
from typing import *
|
||||
from typing import Set, Union
|
||||
|
||||
|
||||
def checkDirExists(dir: Union[str, pathlib.Path]) -> pathlib.Path:
|
||||
"""Prüft, ob ein Verzeichnis existiert. Ist dies nicht möglich, wird eine Exception geworfen.
|
||||
@ -26,19 +25,19 @@ def checkDirExists(dir : Union[str, pathlib.Path]) -> pathlib.Path:
|
||||
|
||||
dir = dir.resolve()
|
||||
if not (dir.exists()):
|
||||
raise Exception("Verzeichnis '" + str(dir) + "' nicht gefunden");
|
||||
raise Exception("Verzeichnis '" + str(dir) + "' nicht gefunden")
|
||||
|
||||
if not (dir.is_dir()):
|
||||
raise Exception("'" + str(dir) + "' ist kein Verzeichnis");
|
||||
return dir;
|
||||
raise Exception("'" + str(dir) + "' ist kein Verzeichnis")
|
||||
return dir
|
||||
|
||||
|
||||
def formatDateTimeForAPplus(v: Union[datetime.datetime, datetime.date, datetime.time]) -> str:
|
||||
"""Formatiert ein Datum oder eine Uhrzeit für APplus"""
|
||||
if (v == None):
|
||||
return "";
|
||||
if v is None:
|
||||
return ""
|
||||
elif isinstance(v, str):
|
||||
return v;
|
||||
return v
|
||||
elif isinstance(v, datetime.datetime):
|
||||
return v.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||
elif isinstance(v, datetime.date):
|
||||
@ -48,6 +47,7 @@ def formatDateTimeForAPplus(v : Union[datetime.datetime, datetime.date, datetime
|
||||
else:
|
||||
return str(v)
|
||||
|
||||
|
||||
def containsOnlyAllowedChars(charset: Set[str], s: str) -> bool:
|
||||
"""Enthält ein String nur erlaubte Zeichen?"""
|
||||
for c in s:
|
||||
|
@ -7,10 +7,10 @@
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
from PyAPplus64 import applus_db
|
||||
import datetime
|
||||
|
||||
|
||||
def test_DBTableIDs1() -> None:
|
||||
ids = applus_db.DBTableIDs();
|
||||
ids = applus_db.DBTableIDs()
|
||||
assert (str(ids) == "{}")
|
||||
ids.add("t1", 1)
|
||||
assert (str(ids) == "{'T1': {1}}")
|
||||
|
@ -9,284 +9,350 @@
|
||||
from PyAPplus64 import sql_utils
|
||||
import datetime
|
||||
|
||||
|
||||
def test_normaliseDBField1() -> None:
|
||||
assert (sql_utils.normaliseDBfield("aAa") == "AAA")
|
||||
assert (sql_utils.normaliseDBfield("a#Aa") == "A#AA")
|
||||
assert (sql_utils.normaliseDBfield("2") == "2")
|
||||
|
||||
|
||||
def test_normaliseDBFieldSet() -> None:
|
||||
assert (sql_utils.normaliseDBfieldSet(set()) == set())
|
||||
assert (sql_utils.normaliseDBfieldSet({"aAa", "b", "c", "2"}) == {"2", "AAA", "B", "C"})
|
||||
|
||||
|
||||
def test_normaliseDBFieldList() -> None:
|
||||
assert (sql_utils.normaliseDBfieldList([]) == [])
|
||||
assert (sql_utils.normaliseDBfieldList(["aAa", "b", "c", "2"]) == ["AAA", "B", "C", "2"])
|
||||
|
||||
|
||||
def test_SqlField1() -> None:
|
||||
assert (str(sql_utils.SqlField("abc")) == "ABC")
|
||||
|
||||
|
||||
def test_SqlField2() -> None:
|
||||
assert (str(sql_utils.SqlField("t.abc")) == "T.ABC")
|
||||
|
||||
|
||||
def test_SqlParam() -> None:
|
||||
assert (str(sql_utils.sqlParam) == "?")
|
||||
|
||||
|
||||
def test_SqlDateTime() -> None:
|
||||
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
||||
assert (str(sql_utils.SqlDateTime(dt)) == "2023-01-12T09:59:12.002")
|
||||
|
||||
|
||||
def test_SqlDate() -> None:
|
||||
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
||||
assert (str(sql_utils.SqlDate(dt)) == "20230112")
|
||||
|
||||
|
||||
def test_formatSqlValueString1() -> None:
|
||||
assert(sql_utils.formatSqlValueString("") == "''");
|
||||
assert (sql_utils.formatSqlValueString("") == "''")
|
||||
|
||||
|
||||
def test_formatSqlValueString2() -> None:
|
||||
assert(sql_utils.formatSqlValueString("abc") == "'abc'");
|
||||
assert (sql_utils.formatSqlValueString("abc") == "'abc'")
|
||||
|
||||
|
||||
def test_formatSqlValueString3() -> None:
|
||||
assert(sql_utils.formatSqlValueString("a b c") == "'a b c'");
|
||||
assert (sql_utils.formatSqlValueString("a b c") == "'a b c'")
|
||||
|
||||
|
||||
def test_formatSqlValueString4() -> None:
|
||||
assert(sql_utils.formatSqlValueString("a \"b\" c") == "'a \"b\" c'");
|
||||
assert (sql_utils.formatSqlValueString("a \"b\" c") == "'a \"b\" c'")
|
||||
|
||||
|
||||
def test_formatSqlValueString5() -> None:
|
||||
assert(sql_utils.formatSqlValueString("a 'b'\nc") == "'a ''b''\nc'");
|
||||
assert (sql_utils.formatSqlValueString("a 'b'\nc") == "'a ''b''\nc'")
|
||||
|
||||
|
||||
def test_formatSqlValue1() -> None:
|
||||
assert(sql_utils.formatSqlValue(2) == "2");
|
||||
assert (sql_utils.formatSqlValue(2) == "2")
|
||||
|
||||
|
||||
def test_formatSqlValue2() -> None:
|
||||
assert(sql_utils.formatSqlValue(2.4) == "2.4");
|
||||
assert (sql_utils.formatSqlValue(2.4) == "2.4")
|
||||
|
||||
|
||||
def test_formatSqlValue3() -> None:
|
||||
assert(sql_utils.formatSqlValue("AA") == "'AA'");
|
||||
assert (sql_utils.formatSqlValue("AA") == "'AA'")
|
||||
|
||||
|
||||
def test_formatSqlValue4() -> None:
|
||||
assert(sql_utils.formatSqlValue(sql_utils.SqlField("aa")) == "AA");
|
||||
assert (sql_utils.formatSqlValue(sql_utils.SqlField("aa")) == "AA")
|
||||
|
||||
|
||||
def test_formatSqlValue5() -> None:
|
||||
assert(sql_utils.formatSqlValue(0) == "0");
|
||||
assert (sql_utils.formatSqlValue(0) == "0")
|
||||
|
||||
|
||||
def test_formatSqlValue6() -> None:
|
||||
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
||||
assert(sql_utils.formatSqlValue(sql_utils.SqlDateTime(dt)) == "'2023-01-12T09:59:12.002'");
|
||||
assert (sql_utils.formatSqlValue(sql_utils.SqlDateTime(dt)) == "'2023-01-12T09:59:12.002'")
|
||||
|
||||
|
||||
def test_SqlConditionTrue() -> None:
|
||||
assert(str(sql_utils.SqlConditionTrue()) == "(1=1)");
|
||||
assert (str(sql_utils.SqlConditionTrue()) == "(1=1)")
|
||||
|
||||
|
||||
def test_SqlConditionFalse() -> None:
|
||||
assert(str(sql_utils.SqlConditionFalse()) == "(1=0)");
|
||||
assert (str(sql_utils.SqlConditionFalse()) == "(1=0)")
|
||||
|
||||
|
||||
def test_SqlConditionBool1() -> None:
|
||||
assert(str(sql_utils.SqlConditionBool(True)) == "(1=1)");
|
||||
assert (str(sql_utils.SqlConditionBool(True)) == "(1=1)")
|
||||
|
||||
|
||||
def test_SqlConditionBool2() -> None:
|
||||
assert(str(sql_utils.SqlConditionBool(False)) == "(1=0)");
|
||||
assert (str(sql_utils.SqlConditionBool(False)) == "(1=0)")
|
||||
|
||||
|
||||
def test_SqlConditionIsNull() -> None:
|
||||
cond = sql_utils.SqlConditionIsNull("AA");
|
||||
assert(str(cond) == "('AA' is null)");
|
||||
cond = sql_utils.SqlConditionIsNull("AA")
|
||||
assert (str(cond) == "('AA' is null)")
|
||||
|
||||
|
||||
def test_SqlConditionIsNotNull() -> None:
|
||||
cond = sql_utils.SqlConditionIsNotNull("AA");
|
||||
assert(str(cond) == "('AA' is not null)");
|
||||
cond = sql_utils.SqlConditionIsNotNull("AA")
|
||||
assert (str(cond) == "('AA' is not null)")
|
||||
|
||||
|
||||
def test_SqlConditionNot() -> None:
|
||||
cond1 = sql_utils.SqlConditionIsNull("AA");
|
||||
cond = sql_utils.SqlConditionNot(cond1);
|
||||
assert(str(cond) == "(not ('AA' is null))");
|
||||
cond1 = sql_utils.SqlConditionIsNull("AA")
|
||||
cond = sql_utils.SqlConditionNot(cond1)
|
||||
assert (str(cond) == "(not ('AA' is null))")
|
||||
|
||||
|
||||
def test_SqlConditionStringStartsWith() -> None:
|
||||
cond = sql_utils.SqlConditionStringStartsWith("f", "a'an")
|
||||
assert(str(cond) == "(left(F, 4) = 'a''an')");
|
||||
assert (str(cond) == "(left(F, 4) = 'a''an')")
|
||||
|
||||
|
||||
def test_SqlConditionIn1() -> None:
|
||||
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), [])
|
||||
assert(str(cond) == "(1=0)");
|
||||
assert (str(cond) == "(1=0)")
|
||||
|
||||
|
||||
def test_SqlConditionIn2() -> None:
|
||||
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), ["a"])
|
||||
assert(str(cond) == "(F = 'a')");
|
||||
assert (str(cond) == "(F = 'a')")
|
||||
|
||||
|
||||
def test_SqlConditionIn3() -> None:
|
||||
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), ["a", "a'A", "b", "c"])
|
||||
assert(str(cond) == "(F in ('a', 'a''A', 'b', 'c'))");
|
||||
assert (str(cond) == "(F in ('a', 'a''A', 'b', 'c'))")
|
||||
|
||||
|
||||
def test_SqlConditionStringNotEmpty1() -> None:
|
||||
cond = sql_utils.SqlConditionFieldStringNotEmpty("f")
|
||||
assert(str(cond) == "(F is not null and F != '')");
|
||||
assert (str(cond) == "(F is not null and F != '')")
|
||||
|
||||
|
||||
def test_SqlConditionEq1() -> None:
|
||||
cond = sql_utils.SqlConditionEq("f1", None)
|
||||
assert(str(cond) == "('f1' is null)");
|
||||
assert (str(cond) == "('f1' is null)")
|
||||
|
||||
|
||||
def test_SqlConditionEq2() -> None:
|
||||
cond = sql_utils.SqlConditionEq(None, "f1")
|
||||
assert(str(cond) == "('f1' is null)");
|
||||
assert (str(cond) == "('f1' is null)")
|
||||
|
||||
|
||||
def test_SqlConditionEq3() -> None:
|
||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), sql_utils.SqlField("f2"))
|
||||
assert(str(cond) == "(F1 = F2)");
|
||||
assert (str(cond) == "(F1 = F2)")
|
||||
|
||||
|
||||
def test_SqlConditionEq4() -> None:
|
||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), "aa'a")
|
||||
assert(str(cond) == "(F1 = 'aa''a')");
|
||||
assert (str(cond) == "(F1 = 'aa''a')")
|
||||
|
||||
|
||||
def test_SqlConditionEq5() -> None:
|
||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), 2)
|
||||
assert(str(cond) == "(F1 = 2)");
|
||||
assert (str(cond) == "(F1 = 2)")
|
||||
|
||||
|
||||
def test_SqlConditionEq6() -> None:
|
||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), True)
|
||||
assert(str(cond) == "(F1 = 1)");
|
||||
assert (str(cond) == "(F1 = 1)")
|
||||
|
||||
|
||||
def test_SqlConditionEq7() -> None:
|
||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), False)
|
||||
assert(str(cond) == "(F1 = 0 OR F1 is null)");
|
||||
assert (str(cond) == "(F1 = 0 OR F1 is null)")
|
||||
|
||||
|
||||
def test_SqlConditionEq8() -> None:
|
||||
cond = sql_utils.SqlConditionEq(True, sql_utils.SqlField("f1"))
|
||||
assert(str(cond) == "(F1 = 1)");
|
||||
assert (str(cond) == "(F1 = 1)")
|
||||
|
||||
|
||||
def test_SqlConditionEq9() -> None:
|
||||
cond = sql_utils.SqlConditionEq(False, sql_utils.SqlField("f1"))
|
||||
assert(str(cond) == "(F1 = 0 OR F1 is null)");
|
||||
assert (str(cond) == "(F1 = 0 OR F1 is null)")
|
||||
|
||||
|
||||
def test_SqlConditionEq10() -> None:
|
||||
cond = sql_utils.SqlConditionEq(False, True)
|
||||
assert(str(cond) == "(1=0)");
|
||||
assert (str(cond) == "(1=0)")
|
||||
|
||||
|
||||
def test_SqlConditionEq11() -> None:
|
||||
cond = sql_utils.SqlConditionEq(True, True)
|
||||
assert(str(cond) == "(1=1)");
|
||||
assert (str(cond) == "(1=1)")
|
||||
|
||||
|
||||
def test_SqlConditionFieldEq1() -> None:
|
||||
cond = sql_utils.SqlConditionFieldEq("f1", None)
|
||||
assert(str(cond) == "(F1 is null)");
|
||||
assert (str(cond) == "(F1 is null)")
|
||||
|
||||
|
||||
def test_SqlConditionFieldEq2() -> None:
|
||||
cond = sql_utils.SqlConditionFieldEq("f1", sql_utils.SqlField("f2"))
|
||||
assert(str(cond) == "(F1 = F2)");
|
||||
assert (str(cond) == "(F1 = F2)")
|
||||
|
||||
|
||||
def test_SqlConditionFieldEq3() -> None:
|
||||
cond = sql_utils.SqlConditionFieldEq("f1", "aa'a")
|
||||
assert(str(cond) == "(F1 = 'aa''a')");
|
||||
assert (str(cond) == "(F1 = 'aa''a')")
|
||||
|
||||
|
||||
def test_SqlConditionFieldEq4() -> None:
|
||||
cond = sql_utils.SqlConditionFieldEq("f1", 2)
|
||||
assert(str(cond) == "(F1 = 2)");
|
||||
assert (str(cond) == "(F1 = 2)")
|
||||
|
||||
|
||||
def test_SqlConditionFieldEq5() -> None:
|
||||
cond = sql_utils.SqlConditionFieldEq("f1", sql_utils.sqlParam)
|
||||
assert(str(cond) == "(F1 = ?)");
|
||||
assert (str(cond) == "(F1 = ?)")
|
||||
|
||||
|
||||
def test_SqlConditionLt1() -> None:
|
||||
cond = sql_utils.SqlConditionLt(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F < '20221212')");
|
||||
assert (str(cond) == "(F < '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionLt2() -> None:
|
||||
cond = sql_utils.SqlConditionLt(2, sql_utils.SqlField("f"))
|
||||
assert(str(cond) == "(2 < F)");
|
||||
assert (str(cond) == "(2 < F)")
|
||||
|
||||
|
||||
def test_SqlConditionGt1() -> None:
|
||||
cond = sql_utils.SqlConditionGt(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F > '20221212')");
|
||||
assert (str(cond) == "(F > '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionGt2() -> None:
|
||||
cond = sql_utils.SqlConditionGt(2, sql_utils.SqlField("f"))
|
||||
assert(str(cond) == "(2 > F)");
|
||||
assert (str(cond) == "(2 > F)")
|
||||
|
||||
|
||||
def test_SqlConditionLe1() -> None:
|
||||
cond = sql_utils.SqlConditionLe(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F <= '20221212')");
|
||||
assert (str(cond) == "(F <= '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionLe2() -> None:
|
||||
cond = sql_utils.SqlConditionLe(2, sql_utils.SqlField("f"))
|
||||
assert(str(cond) == "(2 <= F)");
|
||||
assert (str(cond) == "(2 <= F)")
|
||||
|
||||
|
||||
def test_SqlConditionGe1() -> None:
|
||||
cond = sql_utils.SqlConditionGe(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F >= '20221212')");
|
||||
assert (str(cond) == "(F >= '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionGe2() -> None:
|
||||
cond = sql_utils.SqlConditionGe(2, sql_utils.SqlField("f"))
|
||||
assert(str(cond) == "(2 >= F)");
|
||||
assert (str(cond) == "(2 >= F)")
|
||||
|
||||
|
||||
def test_SqlConditionFieldLt1() -> None:
|
||||
cond = sql_utils.SqlConditionFieldLt("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F < '20221212')");
|
||||
assert (str(cond) == "(F < '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionFieldLe1() -> None:
|
||||
cond = sql_utils.SqlConditionFieldLe("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F <= '20221212')");
|
||||
assert (str(cond) == "(F <= '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionFieldGt1() -> None:
|
||||
cond = sql_utils.SqlConditionFieldGt("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F > '20221212')");
|
||||
assert (str(cond) == "(F > '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionFieldGe1() -> None:
|
||||
cond = sql_utils.SqlConditionFieldGe("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||
assert(str(cond) == "(F >= '20221212')");
|
||||
assert (str(cond) == "(F >= '20221212')")
|
||||
|
||||
|
||||
def test_SqlConditionAnd1() -> None:
|
||||
conj = sql_utils.SqlConditionAnd();
|
||||
assert(str(conj) == "(1=1)");
|
||||
conj = sql_utils.SqlConditionAnd()
|
||||
assert (str(conj) == "(1=1)")
|
||||
|
||||
|
||||
def test_SqlConditionAnd2() -> None:
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
||||
conj = sql_utils.SqlConditionAnd();
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||
conj = sql_utils.SqlConditionAnd()
|
||||
conj.addCondition(cond1)
|
||||
assert(str(conj) == "cond1");
|
||||
assert (str(conj) == "cond1")
|
||||
|
||||
|
||||
def test_SqlConditionAnd3() -> None:
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
||||
conj = sql_utils.SqlConditionAnd();
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||
conj = sql_utils.SqlConditionAnd()
|
||||
conj.addCondition(cond1)
|
||||
conj.addCondition(cond2)
|
||||
assert(str(conj) == "(cond1 AND cond2)");
|
||||
assert (str(conj) == "(cond1 AND cond2)")
|
||||
|
||||
|
||||
def test_SqlConditionAnd4() -> None:
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
||||
cond3 = sql_utils.SqlConditionPrepared("cond3");
|
||||
conj = sql_utils.SqlConditionAnd();
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||
cond3 = sql_utils.SqlConditionPrepared("cond3")
|
||||
conj = sql_utils.SqlConditionAnd()
|
||||
conj.addCondition(cond1)
|
||||
conj.addCondition(cond2)
|
||||
conj.addCondition(cond3)
|
||||
assert(str(conj) == "(cond1 AND cond2 AND cond3)");
|
||||
assert (str(conj) == "(cond1 AND cond2 AND cond3)")
|
||||
|
||||
|
||||
def test_SqlConditionOr1() -> None:
|
||||
conj = sql_utils.SqlConditionOr();
|
||||
assert(str(conj) == "(1=0)");
|
||||
conj = sql_utils.SqlConditionOr()
|
||||
assert (str(conj) == "(1=0)")
|
||||
|
||||
|
||||
def test_SqlConditionOr2() -> None:
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
||||
conj = sql_utils.SqlConditionOr();
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||
conj = sql_utils.SqlConditionOr()
|
||||
conj.addCondition(cond1)
|
||||
assert(str(conj) == "cond1");
|
||||
assert (str(conj) == "cond1")
|
||||
|
||||
|
||||
def test_SqlConditionOr3() -> None:
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
||||
conj = sql_utils.SqlConditionOr();
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||
conj = sql_utils.SqlConditionOr()
|
||||
conj.addCondition(cond1)
|
||||
conj.addCondition(cond2)
|
||||
assert(str(conj) == "(cond1 OR cond2)");
|
||||
assert (str(conj) == "(cond1 OR cond2)")
|
||||
|
||||
|
||||
def test_SqlConditionOr4() -> None:
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
||||
cond3 = sql_utils.SqlConditionPrepared("cond3");
|
||||
conj = sql_utils.SqlConditionOr();
|
||||
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||
cond3 = sql_utils.SqlConditionPrepared("cond3")
|
||||
conj = sql_utils.SqlConditionOr()
|
||||
conj.addCondition(cond1)
|
||||
conj.addCondition(cond2)
|
||||
conj.addCondition(cond3)
|
||||
assert(str(conj) == "(cond1 OR cond2 OR cond3)");
|
||||
assert (str(conj) == "(cond1 OR cond2 OR cond3)")
|
||||
|
||||
|
||||
def test_SqlStatementSelect1() -> None:
|
||||
sql = sql_utils.SqlStatementSelect("tabelle t")
|
||||
@ -323,6 +389,7 @@ def test_SqlStatementSelect2() -> None:
|
||||
sql.addJoin("left join t3 on cond3")
|
||||
assert (str(sql) == "SELECT * FROM t1 left join t2 on cond2 left join t3 on cond3")
|
||||
|
||||
|
||||
def test_SqlStatementSelect4() -> None:
|
||||
sql = sql_utils.SqlStatementSelect("t")
|
||||
sql.where.addCondition("cond1")
|
||||
@ -331,9 +398,10 @@ def test_SqlStatementSelect4() -> None:
|
||||
sql.where.addCondition("cond2")
|
||||
assert (str(sql) == "SELECT * FROM t WHERE ((cond1) AND (cond2))")
|
||||
|
||||
|
||||
def test_SqlStatementSelect5() -> None:
|
||||
sql = sql_utils.SqlStatementSelect("t")
|
||||
cond = sql_utils.SqlConditionOr();
|
||||
cond = sql_utils.SqlConditionOr()
|
||||
sql.where.addCondition(cond)
|
||||
cond.addCondition("cond1")
|
||||
assert (str(sql) == "SELECT * FROM t WHERE (cond1)")
|
||||
@ -341,9 +409,10 @@ def test_SqlStatementSelect5() -> None:
|
||||
cond.addCondition("cond2")
|
||||
assert (str(sql) == "SELECT * FROM t WHERE ((cond1) OR (cond2))")
|
||||
|
||||
|
||||
def test_SqlStatementSelect6() -> None:
|
||||
sql = sql_utils.SqlStatementSelect("t")
|
||||
sql.where = sql_utils.SqlConditionOr();
|
||||
sql.where = sql_utils.SqlConditionOr()
|
||||
sql.where.addCondition("cond1")
|
||||
assert (str(sql) == "SELECT * FROM t WHERE (cond1)")
|
||||
|
||||
|
Reference in New Issue
Block a user