From fb3e1227837aed2917ae0692abb0ef238dd5e375 Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Sat, 18 Jan 2020 13:03:00 +0300 Subject: [PATCH] #RobotDB prefinal --- Sources/pyOpenRPA/Tools/RobotDB/ExcelCom.py | 11 ++ Sources/pyOpenRPA/Tools/RobotDB/HowToUse | 41 +++- .../pyOpenRPA/Tools/RobotDB/ServerSettings.py | 185 +++++++++++++----- Sources/pyOpenRPA/__init__.py | 2 +- Utils/RobotDB/DB.db | Bin 12288 -> 12288 bytes Utils/RobotDB/SettingsExample.py | 6 +- Utils/RobotDB/Test.xlsx | Bin 0 -> 9283 bytes v1.0.37 => v1.0.38 | 0 8 files changed, 188 insertions(+), 57 deletions(-) create mode 100644 Sources/pyOpenRPA/Tools/RobotDB/ExcelCom.py create mode 100644 Utils/RobotDB/Test.xlsx rename v1.0.37 => v1.0.38 (100%) diff --git a/Sources/pyOpenRPA/Tools/RobotDB/ExcelCom.py b/Sources/pyOpenRPA/Tools/RobotDB/ExcelCom.py new file mode 100644 index 00000000..41bf0ea6 --- /dev/null +++ b/Sources/pyOpenRPA/Tools/RobotDB/ExcelCom.py @@ -0,0 +1,11 @@ +import win32com.client as win32 +def OpenWorkbook(xlapp, xlfile): + try: + xlwb = xlapp.Workbooks(xlfile) + except Exception as e: + try: + xlwb = xlapp.Workbooks.Open(xlfile) + except Exception as e: + print(e) + xlwb = None + return(xlwb) \ No newline at end of file diff --git a/Sources/pyOpenRPA/Tools/RobotDB/HowToUse b/Sources/pyOpenRPA/Tools/RobotDB/HowToUse index c2b5e516..c77ebd23 100644 --- a/Sources/pyOpenRPA/Tools/RobotDB/HowToUse +++ b/Sources/pyOpenRPA/Tools/RobotDB/HowToUse @@ -14,10 +14,6 @@ $(document).ready(function() { success: null, dataType: "html" }); - - -SQLInsert - data: [{"TableName":"",RowDict:{"Key1":Value1, "Key2":Value2}}] $(document).ready(function() { @@ -25,9 +21,42 @@ $(document).ready(function() { $("#myid").addClass("highlight"); $.ajax({ type: "POST", - url: "http://localhost:81/", + url: "http://localhost:8081/", data: '[{"TableName":"Test", "RowDict":{"Name":"Name1","Description":"DescTest", "Money":100, "Date":"01.01.2020"}}]', success: null, dataType: "html" }); -}); \ No newline at end of file +}); + + + +/SQLInsert +POST + + +Result: json +{ + "Status":"OK"|"ERROR", #General status (OK or Error in general alg) + "ErrorMessage":"", #Error message if Status = ERROR + "Result":[ + { + "Status":"OK"|"ERROR", #Row status (OK or Error in current insert) + "ErrorMessage":"", #Error message if Status = ERROR + }, + {} + ] +} + +/SQLExportXLS.xlsx +POST +Args: { + "XLSTemplatePath":"C:\\Path\\To\\XLS template.xlsx", + "XLSSheetName":"SheetName", + "OffsetRow": 2, + "OffsetCol":1, + "XLSResultPath": "C:\\Path\\To\\XLS Result.xlsx", + "XLSResultFlagSendInResponse": true, + "XLSResultFlagDeleteAfterSend": true +} + +Result: xls file \ No newline at end of file diff --git a/Sources/pyOpenRPA/Tools/RobotDB/ServerSettings.py b/Sources/pyOpenRPA/Tools/RobotDB/ServerSettings.py index 3c533528..3620dd75 100644 --- a/Sources/pyOpenRPA/Tools/RobotDB/ServerSettings.py +++ b/Sources/pyOpenRPA/Tools/RobotDB/ServerSettings.py @@ -1,59 +1,150 @@ import json +from . import ExcelCom +import os +import sqlite3 +import win32com.client +import time +import pythoncom #Insert in DB def SQLInsert(inRequest,inGlobalDict): inResponseDict = inRequest.OpenRPAResponseDict # Create result JSON - lResultJSON = {"FlagSQLInsert": False} - #Read the body - #ReadRequest - lInputJSON={} - if inRequest.headers.get('Content-Length') is not None: - lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) - lInputByteArray=inRequest.rfile.read(lInputByteArrayLength) - print(lInputByteArray.decode('utf8')) - #Превращение массива байт в объект - lInputJSON=json.loads(lInputByteArray.decode('utf8')) - ######################################## - print(lInputJSON) - import sqlite3 + lResultJSON = {"Status": "OK", "ErrorMessage":"", "Result":[]} + #Set status code 200 + inResponseDict["StatusCode"] = 200 + try: + #Read the body + #ReadRequest + lInputJSON={} + if inRequest.headers.get('Content-Length') is not None: + lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) + lInputByteArray=inRequest.rfile.read(lInputByteArrayLength) + #print(lInputByteArray.decode('utf8')) + #Превращение массива байт в объект + lInputJSON=json.loads(lInputByteArray.decode('utf8')) + ######################################## + conn = sqlite3.connect(inGlobalDict["SQLite"]["DBPath"]) + c = conn.cursor() + # Loop for rows + for lRowItem in lInputJSON: + lRowResult={"Status": "OK", "ErrorMessage":""} + try: + my_dict = lRowItem["RowDict"] + # Insert a row of data + columns = ', '.join(my_dict.keys()) + placeholders = ':'+', :'.join(my_dict.keys()) + query = f'INSERT INTO {lRowItem["TableName"]} (%s) VALUES (%s)' % (columns, placeholders) + c.execute(query, my_dict) + except Exception as e: + lRowResult["Status"]="ERROR" + lRowResult["ErrorMessage"]=str(e) + finally: + lResultJSON["Result"].append(lRowResult) + # Save (commit) the changes + conn.commit() + # We can also close the connection if we are done with it. + # Just be sure any changes have been committed or they will be lost. + conn.close() + except Exception as e: + lResultJSON["Status"]="ERROR" + lResultJSON["ErrorMessage"]=str(e) + finally: + ######################################## + # Send message back to client + message = json.dumps(lResultJSON) + print(message) + # Write content as utf-8 data + inResponseDict["Body"] = bytes(message, "utf8") +################################################ +#Export SQLite to Excel +def SQLExportXLS(inRequest,inGlobalDict): + #Step 1 - read SQLite conn = sqlite3.connect(inGlobalDict["SQLite"]["DBPath"]) c = conn.cursor() # Loop for rows - for lRowItem in lInputJSON: - my_dict = lRowItem["RowDict"] - # Insert a row of data - columns = ', '.join(my_dict.keys()) - placeholders = ':'+', :'.join(my_dict.keys()) - query = f'INSERT INTO {lRowItem["TableName"]} (%s) VALUES (%s)' % (columns, placeholders) - c.execute(query, my_dict) - # Save (commit) the changes - conn.commit() +# for lRowItem in lInputJSON: +# my_dict = lRowItem["RowDict"] +# # Insert a row of data +# columns = ', '.join(my_dict.keys()) +# placeholders = ':'+', :'.join(my_dict.keys()) + query = f'select * from Test' + #create data array + #row = range(0,10) + i = 0 + data_array = [] + for row in c.execute(query): + # use the cursor as an iterable + data_array.append(row) + i += 1 # We can also close the connection if we are done with it. # Just be sure any changes have been committed or they will be lost. conn.close() - ######################################## - # Send message back to client - message = json.dumps(lResultJSON) - # Write content as utf-8 data - inResponseDict["Body"] = bytes(message, "utf8") - -def GetScreenshot(inRequest,inGlobalDict): - # Get Screenshot - def SaveScreenshot(inFilePath): - # grab fullscreen - # Save the entire virtual screen as a PNG - lScreenshot = getScreenAsImage() - lScreenshot.save('screenshot.png', format='png') - # lScreenshot = ScreenshotSecondScreen.grab_screen() - # save image file - # lScreenshot.save('screenshot.png') - # Сохранить файл на диск - SaveScreenshot("Screenshot.png") - lFileObject = open("Screenshot.png", "rb") - # Write content as utf-8 data - inRequest.OpenRPAResponseDict["Body"] = lFileObject.read() - # Закрыть файловый объект - lFileObject.close() + #step 2 - insert in XLS + pythoncom.CoInitialize() + #write the array to an excel file + #excel = win32com.client.Dispatch("Excel.Application") + excel = win32com.client.gencache.EnsureDispatch('Excel.Application') + excel.Visible = True + excel.DisplayAlerts = False + #excel.ScreenUpdating = False + #book = excel.Workbooks.Add() + #sheet = book.Worksheets(1) + #Read input JSON + lInputJSON={} + if inRequest.headers.get('Content-Length') is not None: + lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) + lInputByteArray=inRequest.rfile.read(lInputByteArrayLength) + #print(lInputByteArray.decode('utf8')) + #Превращение массива байт в объект + lInputJSON=json.loads(lInputByteArray.decode('utf8')) + #Config + lOffsetRow = lInputJSON["OffsetRow"] + lOffsetCol = lInputJSON["OffsetCol"] + lXLSTemplatePath = lInputJSON["XLSTemplatePath"] + lXLSSheetName = lInputJSON["XLSSheetName"] + lXLSResultPath = lInputJSON["XLSResultPath"] + lXLSResultFlagSendInResponse = lInputJSON["XLSResultFlagSendInResponse"] + lXLSResultFlagDeleteAfterSend = lInputJSON["XLSResultFlagDeleteAfterSend"] + try: + #excel = win32com.client.gencache.EnsureDispatch('Excel.Application') + book = ExcelCom.OpenWorkbook(excel, lXLSTemplatePath) + sheet = book.Worksheets(lXLSSheetName) + excel.Visible = True + #single loop, writing a row to a range + #Logic + start = time.time() + row = 0 + for line in data_array: + row += 1 + sheet.Range(sheet.Cells(row+lOffsetRow,1+lOffsetCol), sheet.Cells(row+lOffsetRow, len(line)+lOffsetCol)).Value = line + if lXLSResultPath: + book.SaveAs(Filename = lXLSResultPath) + #excel.ScreenUpdating = True + except Exception as e: + print(e) + finally: + # RELEASES RESOURCES + sheet = None + book = None + excel.DisplayAlerts = True + excel.Quit() + excel = None + pythoncom.CoUninitialize() + ##################### + #Step 3 - Send file content to client + ##################### + if lXLSResultFlagSendInResponse and lXLSResultPath: + lFileObject = open(lXLSResultPath, "rb") + # Write content as utf-8 data + inRequest.OpenRPAResponseDict["Body"] = lFileObject.read() + # Закрыть файловый объект + lFileObject.close() + ##################### + #Step 4 - Delete after send + ##################### + if lXLSResultFlagDeleteAfterSend and lXLSResultPath: + if os.path.exists(lXLSResultPath): + os.remove(lXLSResultPath) def SettingsUpdate(inGlobalConfiguration): import os import pyOpenRPA.Orchestrator @@ -70,8 +161,8 @@ def SettingsUpdate(inGlobalConfiguration): # "ResponseDefRequestGlobal": None #Function with str result #} #Orchestrator basic dependencies - {"Method":"POST", "URL": "/", "MatchType": "EqualCase", "ResponseDefRequestGlobal": SQLInsert, "ResponseContentType": "text/html"}, - {"Method":"GET", "URL": "/3rdParty/Semantic-UI-CSS-master/semantic.min.css", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Semantic-UI-CSS-master\\semantic.min.css"), "ResponseContentType": "text/css"} + {"Method":"POST", "URL": "/SQLInsert", "MatchType": "EqualCase", "ResponseDefRequestGlobal": SQLInsert, "ResponseContentType": "application/json"}, + {"Method":"POST", "URL": "/SQLExportXLS.xlsx", "MatchType": "EqualCase", "ResponseDefRequestGlobal": SQLExportXLS, "ResponseContentType": "application/octet-stream"} ] inGlobalConfiguration["Server"]["URLList"]=inGlobalConfiguration["Server"]["URLList"]+lURLList return inGlobalConfiguration \ No newline at end of file diff --git a/Sources/pyOpenRPA/__init__.py b/Sources/pyOpenRPA/__init__.py index eeeef317..1ce87e12 100644 --- a/Sources/pyOpenRPA/__init__.py +++ b/Sources/pyOpenRPA/__init__.py @@ -3,7 +3,7 @@ r""" The OpenRPA package (from UnicodeLabs) """ -__version__ = 'v1.0.37' +__version__ = 'v1.0.38' __all__ = [] __author__ = 'Ivan Maslov ' #from .Core import Robot \ No newline at end of file diff --git a/Utils/RobotDB/DB.db b/Utils/RobotDB/DB.db index e611941f8bc1ba89ed189381fd2959356fb9c565..991a5252cc5b35840565612cd4c95972aa27d894 100644 GIT binary patch delta 142 zcmZojXh@hK%_ulg#+gxYW5N=C6#+g82L5OKXZSbq&)~1%kKni9m*D%scY|*S-weJA zz6d@GK8cNmA9$JM1SVI>%Q4CELnuB7#S5W$AQU%*;(}0|5Q+mru|p^}5Vg5NzFmM( F003sqBKiOT delta 44 zcmZojXh@hK&B!)U#+i|AW5N=CK34t;2L5OKXZSbq&)~1vEU1vizqvxbU4W4l04E;| A#{d8T diff --git a/Utils/RobotDB/SettingsExample.py b/Utils/RobotDB/SettingsExample.py index f8f5fd62..3a97df1b 100644 --- a/Utils/RobotDB/SettingsExample.py +++ b/Utils/RobotDB/SettingsExample.py @@ -14,16 +14,16 @@ def Settings(): }, "Server": { "ListenPort_": "Порт, по которому можно подключиться к демону", - "ListenPort": 81, + "ListenPort": 8081, "ListenURLList": [ { "Description": "Local machine test", "URL_": "Сетевое расположение сервера демона", - "URL": "http://127.0.0.1:81" + "URL": "http://127.0.0.1:8081" } ], "AccessUsers": { #Default - all URL is blocked - "FlagCredentialsAsk": True, #Turn on Authentication + "FlagCredentialsAsk": False, #Turn on Authentication "RuleDomainUserDict": { #("DOMAIN", "USER"): { !!!!!only in upper case!!!! # "MethodMatchURLBeforeList": [ diff --git a/Utils/RobotDB/Test.xlsx b/Utils/RobotDB/Test.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..857e0e24d3389d239edba13a812481518b31e13f GIT binary patch literal 9283 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 literal 0 HcmV?d00001 diff --git a/v1.0.37 b/v1.0.38 similarity index 100% rename from v1.0.37 rename to v1.0.38