apply flake8, remove Python 3.10 Syntax
make sure that the package works with older Python versions: - replace matches with if-then-else - Replace "|" with "Union" - Remove "TypeAbbrev" Make sure taht flake8 produces few warnings. Add github action for automatic checks.
This commit is contained in:
parent
d88469e711
commit
3566c9ba3e
|
@ -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 .
|
|
@ -21,12 +21,12 @@ author = 'Thomas Tuerk'
|
|||
extensions = [
|
||||
'sphinx.ext.duration',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.autosummary',
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = [] # type: ignore
|
||||
exclude_patterns = [] # type: ignore
|
||||
|
||||
language = 'de'
|
||||
|
||||
|
@ -53,4 +53,4 @@ latex_elements = {
|
|||
|
||||
autodoc_type_aliases = {
|
||||
'SqlValue': 'SqlValue'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,11 @@ import PyAPplus64
|
|||
import applus_configs
|
||||
import pathlib
|
||||
|
||||
def main(confFile : pathlib.Path, outfile : str) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
# Einfache SQL-Anfrage
|
||||
def main(confFile: pathlib.Path, outfile: str) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
# Einfache SQL-Anfrage
|
||||
sql1 = ("select Material, count(*) as Anzahl from ARTIKEL "
|
||||
"group by MATERIAL having MATERIAL is not null "
|
||||
"order by Anzahl desc")
|
||||
|
@ -21,7 +22,7 @@ def main(confFile : pathlib.Path, outfile : str) -> None:
|
|||
|
||||
# Sql Select-Statements können auch über SqlStatementSelect zusammengebaut
|
||||
# werden. Die ist bei vielen, komplizierten Bedingungen teilweise hilfreich.
|
||||
sql2 = PyAPplus64.SqlStatementSelect("ARTIKEL")
|
||||
sql2 = PyAPplus64.SqlStatementSelect("ARTIKEL")
|
||||
sql2.addFields("Material", "count(*) as Anzahl")
|
||||
sql2.addGroupBy("MATERIAL")
|
||||
sql2.having.addConditionFieldIsNotNull("MATERIAL")
|
||||
|
@ -33,4 +34,4 @@ def main(confFile : pathlib.Path, outfile : str) -> None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest, "myout.xlsx")
|
||||
main(applus_configs.serverConfYamlTest, "myout.xlsx")
|
||||
|
|
|
@ -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,26 +9,29 @@
|
|||
import pathlib
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
from typing import Optional
|
||||
|
||||
def main(confFile : pathlib.Path, updateDB:bool, docDir:str|None = None) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
if docDir is None:
|
||||
docDir = str(server.scripttool.getInstallPathWebServer().joinpath("DocLib"))
|
||||
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)
|
||||
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()
|
||||
|
||||
for row in server.dbQueryAll(sql):
|
||||
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();
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest, False)
|
||||
main(applus_configs.serverConfYamlTest, False)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#
|
||||
# Dies ist für Administrationszwecke gedacht. Anwendungsbeispiel wäre,
|
||||
# dass ein Artikel mit langem Arbeitsplan und Stückliste im Test-System erstellt wird.
|
||||
# Viele der Positionen enthalten Nachauflöse-Scripte, die im Test-System
|
||||
# Viele der Positionen enthalten Nachauflöse-Scripte, die im Test-System
|
||||
# getestet werden. Diese vielen Scripte per Hand zu kopieren ist aufwändig
|
||||
# und Fehleranfällig und kann mit solchen Admin-Scripten automatisiert werden.
|
||||
|
||||
|
@ -23,19 +23,20 @@ import pathlib
|
|||
import PyAPplus64
|
||||
import applus_configs
|
||||
import logging
|
||||
import yaml
|
||||
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)
|
||||
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
|
||||
|
@ -44,16 +45,15 @@ def main(confFile:pathlib.Path, artikel:str, artikelNeu:str|None=None) -> None:
|
|||
artikelNeu = server.nextNumber("Artikel")
|
||||
|
||||
if not (dArt is None):
|
||||
dArt.setFields({"artikel" : artikelNeu})
|
||||
res = dArt.insert(server);
|
||||
print(res);
|
||||
dArt.setFields({"artikel": artikelNeu})
|
||||
res = dArt.insert(server)
|
||||
print(res)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Logger Einrichten
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# logger = logging.getLogger("PyAPplus64.applus_db");
|
||||
# logger.setLevel(logging.ERROR)
|
||||
|
||||
main(applus_configs.serverConfYamlTest, "my-artikel", artikelNeu="my-artikel-copy")
|
||||
|
||||
|
|
|
@ -11,61 +11,64 @@
|
|||
import datetime
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import pandas as pd # type: ignore
|
||||
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");
|
||||
server: PyAPplus64.APplusServer,
|
||||
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")
|
||||
sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
|
||||
sql.addFieldsTable("w", "MENGE", "MENGE_IST",
|
||||
sql.addFieldsTable("w", "MENGE", "MENGE_IST",
|
||||
"APLAN as ARTIKEL", "NAME as ARTIKELNAME")
|
||||
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
||||
|
||||
sql.where.addConditionFieldGe("w.STATUS", 5)
|
||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||
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);
|
||||
sql.order = "w.UPDDATE"
|
||||
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,
|
||||
lambda r: r.POSITION,
|
||||
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,
|
||||
lambda r: r.BAUFTRAG,
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.BAUFTRAG,
|
||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||
|
||||
colNames = {
|
||||
"BAUFTRAG" : "Betriebsauftrag",
|
||||
"POSITION" : "Pos",
|
||||
"MENGENABWEICHUNG" : "Mengenabweichung",
|
||||
"MENGE" : "Menge",
|
||||
"MENGE_IST" : "Menge-Ist",
|
||||
"ARTIKEL" : "Artikel",
|
||||
"ARTIKELNAME" : "Artikel-Name",
|
||||
"UPDDATE" : "geändert am",
|
||||
"UPDNAME" : "geändert von"
|
||||
"BAUFTRAG": "Betriebsauftrag",
|
||||
"POSITION": "Pos",
|
||||
"MENGENABWEICHUNG": "Mengenabweichung",
|
||||
"MENGE": "Menge",
|
||||
"MENGE_IST": "Menge-Ist",
|
||||
"ARTIKEL": "Artikel",
|
||||
"ARTIKELNAME": "Artikel-Name",
|
||||
"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");
|
||||
server: PyAPplus64.APplusServer,
|
||||
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")
|
||||
|
@ -74,49 +77,53 @@ def ladeAlleWerkstattauftragPosMengenabweichungen(
|
|||
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
||||
|
||||
sql.where.addConditionFieldEq("w.STATUS", 4)
|
||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||
sql.where.addCondition(cond)
|
||||
sql.order="w.UPDDATE"
|
||||
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,
|
||||
lambda r: r.POSITION,
|
||||
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,
|
||||
lambda r: r.BAUFTRAG,
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.BAUFTRAG,
|
||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
lambda r: r.AG,
|
||||
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||
dfOrg,
|
||||
lambda r: r.AG,
|
||||
lambda r: server.makeWebLinkWauftragPos(
|
||||
bauftrag=r.BAUFTRAG, position=r.POSITION, accessid=r.ID))
|
||||
|
||||
|
||||
# Demo zum Hinzufügen einer berechneten Spalte
|
||||
# df['BAUFPOSAG'] = PyAPplus64.pandas.mkDataframeColumn(dfOrg,
|
||||
# df['BAUFPOSAG'] = PyAPplus64.pandas.mkDataframeColumn(dfOrg,
|
||||
# lambda r: "{}.{} AG {}".format(r.BAUFTRAG, r.POSITION, r.AG))
|
||||
|
||||
# Rename Columns
|
||||
colNames = {
|
||||
"BAUFTRAG" : "Betriebsauftrag",
|
||||
"POSITION" : "Pos",
|
||||
"AG" : "AG",
|
||||
"MENGENABWEICHUNG" : "Mengenabweichung",
|
||||
"MENGE" : "Menge",
|
||||
"MENGE_IST" : "Menge-Ist",
|
||||
"ARTIKEL" : "Artikel",
|
||||
"UPDDATE" : "geändert am",
|
||||
"UPDNAME" : "geändert von"
|
||||
"BAUFTRAG": "Betriebsauftrag",
|
||||
"POSITION": "Pos",
|
||||
"AG": "AG",
|
||||
"MENGENABWEICHUNG": "Mengenabweichung",
|
||||
"MENGE": "Menge",
|
||||
"MENGE_IST": "Menge-Ist",
|
||||
"ARTIKEL": "Artikel",
|
||||
"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:
|
||||
if not (year is 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)
|
||||
else:
|
||||
|
@ -124,59 +131,67 @@ def computeInYearMonthCond(field : str, year:int|None=None,
|
|||
else:
|
||||
return None
|
||||
|
||||
def computeFileName(year:int|None=None, month:int|None=None) -> str:
|
||||
if year is None:
|
||||
return 'mengenabweichungen-all.xlsx';
|
||||
|
||||
def computeFileName(year: Optional[int] = None, month: Optional[int] = None) -> str:
|
||||
if year is None:
|
||||
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:
|
||||
|
||||
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:
|
||||
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:
|
||||
cond=computeInYearMonthCond("w.UPDDATE", year=year, month=month)
|
||||
def exportVonBis(server: PyAPplus64.APplusServer, fn: str,
|
||||
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: 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]:
|
||||
|
||||
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]:
|
||||
|
||||
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: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=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);
|
||||
|
||||
# Ausgaben
|
||||
exportYearMonth(server, cyear, cmonth) # Aktueller Monat
|
||||
exportYearMonth(server, pyear, pmonth) # Vorheriger Monat
|
||||
(pyear, pmonth) = computePreviousMonthYear(cyear, cmonth)
|
||||
|
||||
# Ausgaben
|
||||
exportYearMonth(server, cyear, cmonth) # Aktueller Monat
|
||||
exportYearMonth(server, pyear, pmonth) # Vorheriger Monat
|
||||
# export(cyear) # aktuelles Jahr
|
||||
# export(cyear-1) # letztes Jahr
|
||||
# export() # alles
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest)
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
import PySimpleGUI as sg # type: ignore
|
||||
import PySimpleGUI as sg # type: ignore
|
||||
import mengenabweichung
|
||||
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,14 +25,17 @@ 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
|
||||
|
||||
(bis, bisOK) = parseDate(bisS)
|
||||
if not bisOK: return
|
||||
|
||||
if (fileS is None) or fileS == '':
|
||||
def createFile(server: PyAPplus64.APplusServer, fileS: str, vonS: str, bisS: str) -> None:
|
||||
(von, vonOK) = parseDate(vonS)
|
||||
if not vonOK:
|
||||
return
|
||||
|
||||
(bis, bisOK) = parseDate(bisS)
|
||||
if not bisOK:
|
||||
return
|
||||
|
||||
if (fileS is None) or fileS == '':
|
||||
sg.popup_error("Es wurde keine Ausgabedatei ausgewählt.")
|
||||
return
|
||||
else:
|
||||
|
@ -41,23 +45,24 @@ 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:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
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 = [
|
||||
[sg.Text(('Bitte geben Sie an, für welchen Zeitraum die '
|
||||
'Mengenabweichungen ausgegeben werden sollen:'))],
|
||||
[sg.Text('Von (einschließlich)', size=(15,1)), sg.InputText(key='Von'),
|
||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||
[sg.Text('Von (einschließlich)', size=(15, 1)), sg.InputText(key='Von'),
|
||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||
target="Von", format='%d.%m.%Y')],
|
||||
[sg.Text('Bis (ausschließlich)', size=(15,1)), sg.InputText(key='Bis'),
|
||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||
[sg.Text('Bis (ausschließlich)', size=(15, 1)), sg.InputText(key='Bis'),
|
||||
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",
|
||||
file_types = (('Excel Files', '*.xlsx'),),
|
||||
default_extension = ".xlsx")],
|
||||
[sg.Button("Aktueller Monat"), sg.Button("Letzter Monat"),
|
||||
[sg.Text('Ausgabedatei', size=(15, 1)), sg.InputText(key='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"),
|
||||
sg.Button("Aktuelles Jahr"), sg.Button("Letztes Jahr")],
|
||||
[sg.Button("Speichern"), sg.Button("Beenden")]
|
||||
]
|
||||
|
@ -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),
|
||||
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)
|
||||
|
|
|
@ -13,40 +13,41 @@
|
|||
import pathlib
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import lxml.etree as ET # type: ignore
|
||||
from typing import Optional, Union
|
||||
|
||||
def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
print ("\n\nSysConf Lookups:")
|
||||
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 (" 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("\n\nSysConf Lookups:")
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest)
|
||||
main(applus_configs.serverConfYamlTest)
|
||||
|
|
|
@ -27,4 +27,4 @@ from .sql_utils import (
|
|||
try:
|
||||
from . import pandas
|
||||
except:
|
||||
pass
|
||||
pass
|
||||
|
|
|
@ -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
|
||||
|
@ -17,18 +15,17 @@ from . import sql_utils
|
|||
import yaml
|
||||
import urllib.parse
|
||||
from zeep import Client
|
||||
import pyodbc # type: ignore
|
||||
from typing import *
|
||||
import pyodbc # type: ignore
|
||||
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.
|
||||
|
||||
|
||||
:param db_settings: die Einstellungen für die Verbindung mit der Datenbank
|
||||
:type db_settings: APplusDBSettings
|
||||
:param server_settings: die Einstellungen für die Verbindung mit dem APplus App Server
|
||||
|
@ -36,46 +33,48 @@ 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.APplusAppServerSettings,
|
||||
web_settings: applus_server.APplusWebServerSettings):
|
||||
|
||||
self.db_settings : applus_db.APplusDBSettings = db_settings
|
||||
self.db_settings: applus_db.APplusDBSettings = db_settings
|
||||
"""Die Einstellungen für die Datenbankverbindung"""
|
||||
|
||||
self.web_settings : applus_server.APplusWebServerSettings = web_settings
|
||||
self.web_settings: applus_server.APplusWebServerSettings = web_settings
|
||||
"""Die Einstellungen für die Datenbankverbindung"""
|
||||
|
||||
self.db_conn = db_settings.connect()
|
||||
self.db_conn = db_settings.connect()
|
||||
"""
|
||||
Eine pyodbc-Connection zur APplus DB. Diese muss genutzt werden, wenn mehrere Operationen in einer Transaktion
|
||||
genutzt werden sollen. Ansonsten sind die Hilfsmethoden wie :meth:`APplusServer.dbQuery` zu bevorzugen.
|
||||
genutzt werden sollen. Ansonsten sind die Hilfsmethoden wie :meth:`APplusServer.dbQuery` zu bevorzugen.
|
||||
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_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")
|
||||
|
||||
def reconnectDB(self) -> None:
|
||||
try:
|
||||
self.db_conn.close()
|
||||
self.db_conn.close()
|
||||
except:
|
||||
pass
|
||||
self.db_conn = self.db_settings.connect()
|
||||
|
||||
def completeSQL(self, sql : sql_utils.SqlStatement, raw:bool=False) -> str:
|
||||
def completeSQL(self, sql: sql_utils.SqlStatement, raw: bool = False) -> str:
|
||||
"""
|
||||
Vervollständigt das SQL-Statement. Es wird z.B. der Mandant hinzugefügt.
|
||||
|
||||
|
||||
:param sql: das SQL Statement
|
||||
:type sql: sql_utils.SqlStatement
|
||||
:param raw: soll completeSQL ausgeführt werden? Falls True, wird die Eingabe zurückgeliefert
|
||||
|
@ -86,55 +85,55 @@ 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
|
||||
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]:
|
||||
def dbQuerySingleValues(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Sequence[Any]:
|
||||
"""Führt eine SQL Query aus, die nur eine Spalte zurückliefern soll."""
|
||||
return self.dbQueryAll(sql, *args, raw=raw, apply=lambda r: r[0])
|
||||
|
||||
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.
|
||||
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]:
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
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:
|
||||
def getClient(self, package: str, name: str) -> Client:
|
||||
"""Erzeugt einen zeep - Client.
|
||||
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
|
||||
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
||||
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
||||
resultierende client "client" genannt, dann kann
|
||||
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
|
||||
des SQLs angefordert werden.
|
||||
|
@ -146,9 +145,9 @@ class APplusServer:
|
|||
:return: den Client
|
||||
:rtype: Client
|
||||
"""
|
||||
return self.server_conn.getClient(package, name);
|
||||
return self.server_conn.getClient(package, name)
|
||||
|
||||
def getTableFields(self, table:str, isComputed:Optional[bool]=None) -> Set[str]:
|
||||
def getTableFields(self, table: str, isComputed: Optional[bool] = None) -> Set[str]:
|
||||
"""
|
||||
Liefert die Namen aller Felder einer Tabelle.
|
||||
|
||||
|
@ -158,34 +157,32 @@ 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):
|
||||
join.on.addConditionFieldEq("c.is_computed", isComputed)
|
||||
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]]:
|
||||
def getUniqueFieldsOfTable(self, table: str) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||
müssen.
|
||||
"""
|
||||
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)
|
||||
|
||||
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);
|
||||
|
||||
|
||||
def mkUseXMLRowInsert(self, table : str) -> applus_usexml.UseXmlRowInsert:
|
||||
def mkUseXMLRowInsert(self, table: str) -> applus_usexml.UseXmlRowInsert:
|
||||
"""
|
||||
Erzeugt ein Objekt zum Einfügen eines neuen DB-Eintrags.
|
||||
|
||||
|
||||
:param table: DB-Tabelle in die eingefügt werden soll
|
||||
:type table: str
|
||||
:return: das XmlRow-Objekt
|
||||
|
@ -194,13 +191,13 @@ class APplusServer:
|
|||
|
||||
return applus_usexml.UseXmlRowInsert(self, table)
|
||||
|
||||
def mkUseXMLRowUpdate(self, table : str, id : int) -> applus_usexml.UseXmlRowUpdate:
|
||||
def mkUseXMLRowUpdate(self, table: str, id: int) -> applus_usexml.UseXmlRowUpdate:
|
||||
return applus_usexml.UseXmlRowUpdate(self, table, id)
|
||||
|
||||
def mkUseXMLRowInsertOrUpdate(self, table : str) -> applus_usexml.UseXmlRowInsertOrUpdate:
|
||||
def mkUseXMLRowInsertOrUpdate(self, table: str) -> applus_usexml.UseXmlRowInsertOrUpdate:
|
||||
"""
|
||||
Erzeugt ein Objekt zum Einfügen oder Updaten eines DB-Eintrags.
|
||||
|
||||
|
||||
:param table: DB-Tabelle in die eingefügt werden soll
|
||||
:type table: string
|
||||
:return: das XmlRow-Objekt
|
||||
|
@ -209,70 +206,69 @@ 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 mkUseXMLRowDelete(self, table:str, id:int) -> applus_usexml.UseXmlRowDelete :
|
||||
return applus_usexml.UseXmlRowDelete(self, table, id)
|
||||
|
||||
def execUseXMLRowDelete(self, table:str, id:int) -> None:
|
||||
def execUseXMLRowDelete(self, table: str, id: int) -> None:
|
||||
delRow = self.mkUseXMLRowDelete(table, id)
|
||||
delRow.delete();
|
||||
delRow.delete()
|
||||
|
||||
def nextNumber(self, obj : str) -> str:
|
||||
def nextNumber(self, obj: str) -> str:
|
||||
"""
|
||||
Erstellt eine neue Nummer für das Objekt und legt diese Nummer zurück.
|
||||
"""
|
||||
return self.client_nummer.service.nextNumber(obj)
|
||||
|
||||
def makeWebLink(self, base : str, **kwargs : Any) -> str :
|
||||
def makeWebLink(self, base: str, **kwargs: Any) -> str:
|
||||
if not self.web_settings.baseurl:
|
||||
raise Exception("keine Webserver-BaseURL gesetzt");
|
||||
|
||||
url = str(self.web_settings.baseurl) + base;
|
||||
raise Exception("keine Webserver-BaseURL gesetzt")
|
||||
|
||||
url = str(self.web_settings.baseurl) + 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);
|
||||
def makeWebLinkWauftragPos(self, **kwargs: Any) -> str:
|
||||
return self.makeWebLink("wp/wauftragPosRec.aspx", **kwargs)
|
||||
|
||||
def makeWebLinkWauftrag(self, **kwargs : Any) -> str :
|
||||
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs);
|
||||
def makeWebLinkWauftrag(self, **kwargs: Any) -> str:
|
||||
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs)
|
||||
|
||||
def makeWebLinkBauftrag(self, **kwargs : Any) -> str :
|
||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs);
|
||||
def makeWebLinkBauftrag(self, **kwargs: Any) -> str:
|
||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs)
|
||||
|
||||
|
||||
|
||||
def applusFromConfigDict(yamlDict:Dict[str, Any], user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
||||
def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||
"""Läd Einstellungen aus einer Config und erzeugt daraus ein APplus-Objekt"""
|
||||
if user is None or user=='':
|
||||
user = yamlDict["appserver"]["user"]
|
||||
if env is None or env=='':
|
||||
env = yamlDict["appserver"]["env"]
|
||||
if user is None or user == '':
|
||||
user = yamlDict["appserver"]["user"]
|
||||
if env is None or env == '':
|
||||
env = yamlDict["appserver"]["env"]
|
||||
app_server = applus_server.APplusAppServerSettings(
|
||||
appserver=yamlDict["appserver"]["server"],
|
||||
appserverPort=yamlDict["appserver"]["port"],
|
||||
user=user, # type: ignore
|
||||
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)
|
||||
)
|
||||
dbparams = applus_db.APplusDBSettings(
|
||||
server=yamlDict["dbserver"]["server"],
|
||||
server=yamlDict["dbserver"]["server"],
|
||||
database=yamlDict["dbserver"]["db"],
|
||||
user=yamlDict["dbserver"]["user"],
|
||||
password=yamlDict["dbserver"]["password"]);
|
||||
return APplusServer(dbparams, app_server, web_server);
|
||||
user=yamlDict["dbserver"]["user"],
|
||||
password=yamlDict["dbserver"]["password"])
|
||||
return APplusServer(dbparams, app_server, web_server)
|
||||
|
||||
def applusFromConfigFile(yamlfile : 'FileDescriptorOrPath',
|
||||
user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
||||
|
||||
def applusFromConfigFile(yamlfile: 'FileDescriptorOrPath',
|
||||
user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||
yamlDict = {}
|
||||
with open(yamlfile, "r") as stream:
|
||||
|
@ -280,8 +276,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:
|
||||
|
||||
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,29 +6,27 @@
|
|||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
import pyodbc # type: ignore
|
||||
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:
|
||||
"""
|
||||
Einstellungen, mit welcher DB sich verbunden werden soll.
|
||||
"""
|
||||
|
||||
def __init__(self, server : str, database : str, user : str, password : str):
|
||||
|
||||
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
|
||||
|
@ -37,7 +35,7 @@ class APplusDBSettings:
|
|||
"Server="+self.server+";"
|
||||
"Database="+self.database+";"
|
||||
"UID="+self.user+";"
|
||||
"PWD="+self.password + ";")
|
||||
"PWD="+self.password + ";")
|
||||
|
||||
def connect(self) -> pyodbc.Connection:
|
||||
"""Stellt eine neue Verbindung her und liefert diese zurück.
|
||||
|
@ -45,22 +43,23 @@ class APplusDBSettings:
|
|||
return pyodbc.connect(self.getConnectionString())
|
||||
|
||||
|
||||
|
||||
def row_to_dict(row : pyodbc.Row) -> Dict[str, Any]:
|
||||
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:
|
||||
|
||||
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,
|
||||
*args : Any,
|
||||
apply : Optional[Callable[[pyodbc.Row], Any]]=None) -> Sequence[Any]:
|
||||
cnxn: pyodbc.Connection,
|
||||
sql: SqlStatement,
|
||||
*args: Any,
|
||||
apply: Optional[Callable[[pyodbc.Row], Any]] = None) -> Sequence[Any]:
|
||||
"""
|
||||
Führt eine SQL Query direkt aus und liefert alle Zeilen zurück.
|
||||
Wenn apply gesetzt ist, wird die Funktion auf jeder Zeile ausgeführt und das Ergebnis ausgeben, die nicht None sind.
|
||||
|
@ -69,48 +68,52 @@ 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:
|
||||
|
||||
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]:
|
||||
|
||||
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:
|
||||
|
||||
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]] :
|
||||
|
||||
def getUniqueFieldsOfTable(cnxn: pyodbc.Connection, table: str) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||
müssen.
|
||||
"""
|
||||
|
||||
|
@ -123,7 +126,7 @@ def getUniqueFieldsOfTable(cnxn : pyodbc.Connection, table : str) -> Dict[str, L
|
|||
sql.addFields("i.name AS INDEX_NAME", "COL_NAME(ic.OBJECT_ID,ic.column_id) AS COL")
|
||||
_logSQLWithArgs(sql)
|
||||
|
||||
indices : Dict[str, List[str]] = {}
|
||||
indices: Dict[str, List[str]] = {}
|
||||
with cnxn.cursor() as cursor:
|
||||
cursor.execute(str(sql))
|
||||
for row in cursor:
|
||||
|
@ -135,31 +138,31 @@ def getUniqueFieldsOfTable(cnxn : pyodbc.Connection, table : str) -> Dict[str, L
|
|||
|
||||
class DBTableIDs():
|
||||
"""Klasse, die Mengen von IDs gruppiert nach Tabellen speichert"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.data : Dict[str, Set[int]]= {}
|
||||