Compare commits

...

51 Commits

Author SHA1 Message Date
Владислав Клычков d7d0395a11 Merge branch 'dev-floidd' into 'dev-fastapi'
1 year ago
Vladislav Klychkov d8c955826f mouseserach return fix
1 year ago
Vladislav Klychkov c21e476c83 new auth form
1 year ago
Vladislav Klychkov 15c617d948 + sphinx docs UIWeb fix
1 year ago
Vladislav Klychkov 6a9de8222d + inFlagRaiseException in UIDesktop + studio test
1 year ago
Vladislav Klychkov dde8a29117 WebURLConnectFolder fix
1 year ago
Vladislav Klychkov aaefa6ab2a new bad auth web page
1 year ago
Vladislav Klychkov 7f01b9c66e changelog after fix issues
1 year ago
Vladislav Klychkov c16dde0fbb fix UIDesktop Get_UIOList & UIWeb WebUserUACHierarchyGet
1 year ago
Vladislav Klychkov b265823b98 fix jupyter start + install autodocsumm
1 year ago
Vladislav Klychkov c7a14db2d4 Rework UIOSelectorClick + new UIOSelectorSetValue (UIWeb)
1 year ago
Vladislav Klychkov bd8ddb0b21 new browser options + PagePrint
1 year ago
Иван Маслов dac8c23beb Merge branch 'dev-fastapi' of https://gitlab.com/UnicodeLabs/OpenRPA.git into dev-fastapi
1 year ago
Иван Маслов 6db4724cc5 add fast api requirements multipart
1 year ago
Ivan Maslov 8ff1d3abab remove print from Box
1 year ago
Ivan Maslov 6d46ab10cd ...
1 year ago
Ivan Maslov d350e02057 LINUX minor fix in url (ORC)
1 year ago
Иван Маслов a218ba057f log
1 year ago
Иван Маслов d1189657c5 orc: - - Права доступа в случае незаявленного пользователя (Hotfix)
1 year ago
Иван Маслов 04b20f0875 Новая панель управления (SIMPLE_01, быстрая настройка робота из оркестратора)
1 year ago
Иван Маслов 095e6feb94 Create ORC CP SIMPLE_01
1 year ago
Иван Маслов f3d3bcce98 add new control panel simple 01 (prototype)
1 year ago
Иван Маслов 4bd862c911 fix sphinx requirements
1 year ago
Иван Маслов 7a78d69afa Дочинить автогенерацию сфинкс (чтобы не зависела от ОС)
2 years ago
Иван Маслов 781988e0a9 wiki in progress
2 years ago
Иван Маслов 4e868f6898 АГТ: Минорная правка, убрать сообщение об ошибке, если от орка пришел None
2 years ago
Иван Маслов 89087dc7fe orc: fix issue 21 on the gitlab
2 years ago
Иван Маслов 55f238e07f debug
2 years ago
Иван Маслов f7ea120222 debug
2 years ago
Иван Маслов 0b72708703 Merge branch 'dev-fastapi' of https://gitlab.com/UnicodeLabs/OpenRPA into dev-fastapi
2 years ago
Иван Маслов 45748afbc9 Orc: web ui rdp fix Need test
2 years ago
Ivan Maslov 773fd2b148 Merge branch 'prd' into dev-fastapi
2 years ago
Ivan Maslov 04c56ffbf7 fix unix lf in jupyter
2 years ago
Ivan Maslov d2847c43f2 fix unix LF
2 years ago
Иван Маслов 1ac3fd2a74 Add & test pyOpenRPA.Utils.Disk>TemplateFile/Folder
2 years ago
Иван Маслов 29a17654e9 help docs
2 years ago
Иван Маслов 7d5eeb6f88 fix start when disk D (another of dick C)
2 years ago
Ivan Maslov 585ac0d35c orc web - new view action : expand full screen/ compress
2 years ago
Ivan Maslov 7af278e208 cp demo: add buyer 01, improve cp hr, oper, implement one python configure cmd in scripts
2 years ago
Ivan Maslov 90577582af Orc: OrchestratorRerunAsAdmin save the prev process env
2 years ago
Иван Маслов b1e6932180 async fix done!
2 years ago
Иван Маслов d58008833b test
2 years ago
Иван Маслов dd420cc3d2 fix
2 years ago
Иван Маслов f96ce9ab58 server data & log processed - need test
2 years ago
Ivan Maslov b6fbe1bba0 in progress
2 years ago
Ivan Maslov 7def532500 async long url in progress
2 years ago
Ivan Maslov 62594d3db0 Merge branch 'dev-fastapi' of https://gitlab.com/UnicodeLabs/OpenRPA into dev-fastapi
2 years ago
Ivan Maslov 13ed13af9c add fastapi app get
2 years ago
Иван Маслов cf29f8a0ce improove BC in server orc
2 years ago
Ivan Maslov 3fdc316703 requirements fixes + Reports > Logs folder
2 years ago
Ivan Maslov cac73eec00 add requirements
2 years ago

@ -0,0 +1,29 @@
$('.ui.dropdown')
.dropdown('clear')
;
var content = [
];
let i = 1
document.getElementById("buyNumber").innerHTML = i
$('.ui.search')
.search({
source: content
})
;
function buy_confirm() {
j = String(i)
content.push({ title: j })
$('.ui.search').search({source: content})
i=i+1;
document.getElementById("buyNumber").innerHTML = i;
$('.ui.dropdown').dropdown('clear')
}

@ -0,0 +1,42 @@
from pyOpenRPA import Orchestrator
from pyOpenRPA.Orchestrator.Managers import ControlPanel
import time
import os
import sys
import socket
import datetime
import urllib.parse
from pyOpenRPA.Orchestrator.Server import app
import threading
from fastapi import Depends
from fastapi.responses import PlainTextResponse
from fastapi.responses import FileResponse
time.sleep(1.5)
g_cp_name_str = "BUYER_01"
g_repo_name_str = os.path.abspath(__file__).split("\\")[-5]
g_repo_package_cp_path_str = os.path.abspath("\\".join(os.path.abspath(__file__).split("\\")[:-1]))
# Подключени файлов связанных с роботом-казначеем01
@app.get(path="/BUYER_01/scripts",tags=["BUYER_01"])
def get_file():
return FileResponse("Demo\\BUYER_01\\buyer_01.js")
# User settings
g_host_str = socket.gethostname().upper() # Identify PC
g_control_panel = ControlPanel(inControlPanelNameStr=g_cp_name_str,
inRefreshHTMLJinja2TemplatePathStr=os.path.join(g_repo_package_cp_path_str, "index.html"),
inJinja2TemplateRefreshBool=True,inRobotNameStr=g_repo_name_str)
g_jinja_context_dict = {"settings": sys.modules[__name__],
"urllib_parse_quote_plus": urllib.parse.quote_plus, "g_host_str": g_host_str, "g_repo_name_str": g_repo_name_str}
g_control_panel.Jinja2DataUpdateDictSet(inJinja2DataUpdateDict=g_jinja_context_dict)

@ -0,0 +1,82 @@
<!doctype html>
<html lang="en">
<head>
<title> Treasurer </title>
</head>
<body>
<div class="card" style="width:450px;" id="card1">
<div class="content">
<center><h1>ЗАКУПЩИК</h1></center><br>
<div class="ui message">
<div class="header">Применение:</div>
<p>Робот-закупщик осуществляет закупку товаров по выбранным параметрам</p>
</div>
<h4><span>Оформление закупки</span></h4>
<div class="meta">
<div>Номер закупки - <span id="buyNumber"></span></div>
</div>
<h2 class="ui header">
<i class="shopping cart icon"></i>
<div class="content">Выбор товара</div>
</h2>
<div class="ui selection dropdown">
<input type="hidden" name="Product">
<i class="dropdown icon"></i>
<div class="default text">Товар</div>
<div class="menu">
<div class="item" data-value="1">Набор резисторов 200 штук, 0.25 Вт, 1%</div>
<div class="item" data-value="0">Набор резисторов 200 штук, 1.00 Вт, 5%</div>
</div>
</div>
<h2 class="ui header">
<i class="truck icon"></i>
<div class="content">Выбор адреса доставки</div>
</h2>
<div class="ui selection dropdown">
<input type="hidden" name="Adress">
<i class="dropdown icon"></i>
<div class="default text">Адресс</div>
<div class="menu">
<div class="item" data-value="1">Г. Москва, ул.Московская, д.15, к2</div>
<div class="item" data-value="0">Г. Санкт-Петербург, пр. Культуры, д.21</div>
</div>
</div>
<h2 class="ui header">
<i class="ruble sign icon"></i>
<div class="content">Номер счета для оплаты</div>
</h2>
<div class="ui selection dropdown">
<input type="hidden" name="Account">
<i class="dropdown icon"></i>
<div class="default text">Номер счета</div>
<div class="menu">
<div class="item" data-value="1">11111.11111111111.11111</div>
<div class="item" data-value="0">00000.11111111111.11111</div>
</div>
</div>
<br><br>
<button class="ui secondary button" onclick="buy_confirm()">
Оформить
</button>
</div>
</div>
<script src="/BUYER_01/scripts"></script>
</body>
</html>

@ -56,7 +56,7 @@ html = f'''<!doctype html>
<body>
<div class="card" style="width:450px;" id="card1">
<div class="card" style="width:550px;" id="card1">
<div class="content">
<center><h1>{title}</h1></center><br>
<div class="ui message">

@ -11,7 +11,7 @@
<body>
<div class="card" style="width:450px;" id="card1">
<div class="card" style="width:550px;" id="card1">
<div class="content">
<center><h1>КАДРОВИК</h1></center><br>
<div class="ui message">

@ -1,4 +1,4 @@
<div class="card" style="width: 450px;"">
<div class="card" style="width: 350px;"">
<div class="content">
<div class="right floated mini ui ">
OPER_00

@ -0,0 +1,123 @@
from pyOpenRPA.Tools import CrossOS
import os
import sys
import time
import threading
from pyOpenRPA import Orchestrator
from pyOpenRPA.Orchestrator.Server import app
from fastapi import Depends, Body
from fastapi.responses import PlainTextResponse
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
time.sleep(1.5) # Определить очередность в панели управления
cp_path_str = CrossOS.PathJoinList(CrossOS.PathSplitList(__file__)[:-1]) # ПАПКА ПАНЕЛИ УПРАВЛЕНИЯ (АВТОИДЕНТИФИКАЦИЯ ПО ТЕКУЩЕМУ ФАЙЛУ)
# # # # # # # # # # # # # # БЛОК # # # # # ПАРАМЕТРОВ # # # # # НАЧАЛО # # # # # # # # # # #
# ПАРАМЕТРЫ АГЕНТА
agent_hostname_str = "" # ЕСЛИ НЕ УКАЗЫВАТЬ - ОРКЕСТРАТОР БУДЕТ ЗАПУСКАТЬ ПРОЦЕСС НА СВОЕЙ УЗ
agent_username_str = ""
# ПАРАМЕТРЫ РОБОТА
robot_logs_path_str = os.path.join(cp_path_str, "..", "..", "Logs") # ПУТЬ К ПАПКЕ С ЛОГАМИ ВИДА гггг_мм_дд.log
robot_key_str = "SIMPLE_01" # КОД РОБОТА (ТЕКСТОВЫЙ ИДЕНТИФИКАТОР В ОБЩЕМ ПРОСТРАНСТВЕ ОРКЕСТРАТОРА)
robot_start_path_str = "notepad" # ПУТЬ К ФАЙЛУ ЗАПУСКА ПРОЦЕССА
robot_process_wo_exe_str = "notepad" # НАИМЕНОВАНИЕ ПРОЦЕССА РОБОТА !БЕЗ УПОМИНАНИЯ РАСШИРЕНИЯ .EXE!
# ПАРАМЕТРЫ ДОПОЛНИТЕЛЬНЫЕ
additional_folder_path_str = os.path.join(cp_path_str, "..", "..", "Readme") # ПУТЬ К ПАПКЕ С ДОПОЛНИТЕЛЬНЫМИ ДОКУМЕНТАМИ - ОТОБРАЖАЮТСЯ НА ПАНЕЛИ ЗАДАЧ
howto_url_str = f"/{robot_key_str}/howto.docx" # URL ССЫЛКА НА ИНСТРУКЦИЮ. НАЧИНАТЬ ОБЯЗАТЕЛЬНО С /
howto_path_str = os.path.join(cp_path_str, "how_to.docx") # ПУТЬ К ИНСТРУКЦИИ НА ДИСКЕ
# # # # # # # # # # # # # # БЛОК # # # # # ПАРАМЕТРОВ # # # # # КОНЕЦ # # # # # # # # # # #
# ПОДКЛЮЧЕНИЕ ПАПКИ С ПАНЕЛЬЮ УПРАВЛЕНИЯ. ДОСТУПНА БУДЕТ ПО URL f"/{robot_key_str}/folder"
app.mount(f"/{robot_key_str}/folder", StaticFiles(directory=os.path.abspath(cp_path_str)), name=f"{robot_key_str}_folder")
# ПОДКЛЮЧЕНИЕ ИНСТРУКЦИИ ПОЛЬЗОВАТЕЛЯ
@app.get(path=howto_url_str,tags=[f"{robot_key_str}"])
def get_howto():
return FileResponse(os.path.abspath(howto_path_str))
# ПОДКЛЮЧЕНИЕ ПАПКИ С ЛОГАМИ
app.mount(f"/{robot_key_str}/logs", StaticFiles(directory=os.path.abspath(robot_logs_path_str)), name=f"{robot_key_str}-logs")
# ПОДКЛЮЧЕНИЕ ПАПКИ С ДОП. ДОКУМЕНТАМИ
app.mount(f"/{robot_key_str}/additional", StaticFiles(directory=os.path.abspath(additional_folder_path_str)), name=f"{robot_key_str}-additional")
def additional_files():
files = os.listdir(additional_folder_path_str)
return files[::-1]
# ПРОВЕРКА ПРОЦЕССА (ЗАПУЩЕН ИЛИ ОСТАНОВЛЕН) ВНИМАНИЕ! АСИНХРОННАЯ ПРОВЕРКА, ЧТОБЫ НЕ ЗАВИСАЛ ПОТОК ОБРАБОТКИ (СЕТЕВОЕ ВЗАИМОДЕЙСТВИЕ)
robot_is_started_bool = False
def robot_is_started():
global robot_is_started_bool
if agent_hostname_str:
process_list_guid_str = Orchestrator.AgentProcessWOExeUpperUserListGet(
inHostNameStr=agent_hostname_str, inUserStr=agent_username_str)
process_list = Orchestrator.AgentActivityItemReturnGet(inGUIDStr=process_list_guid_str, inTimeoutSecFloat=60.0)
if robot_process_wo_exe_str.upper() in process_list: robot_is_started_bool=True
else: robot_is_started_bool=False
else:
if robot_process_wo_exe_str.upper() in Orchestrator.ProcessListGet()['ProcessWOExeUpperList']: robot_is_started_bool=True
else: robot_is_started_bool=False
robot_status_interval_sec_float = 3.0 # ИНТЕРВАЛ ПРОВЕРКИ АКТИВНОСТИ ПРОЦЕССА РОБОТА
def loop_robot_is_started():
global robot_status_interval_sec_float
while True:
try:
robot_is_started()
except Exception as e:
pass
time.sleep(robot_status_interval_sec_float)
threading.Thread(target=loop_robot_is_started).start()
# ФУНКЦИЯ ЗАПУСКА ФАЙЛА
@app.post(path=f"/{robot_key_str}/action/start",tags=[f"{robot_key_str}"])
def robot_start():
cmd_str = robot_start_path_str
global robot_is_started_bool
if agent_hostname_str:
Orchestrator.AgentOSCMD(inHostNameStr=agent_hostname_str, inUserStr=agent_username_str,
inCMDStr=cmd_str, inRunAsyncBool=True,
inSendOutputToOrchestratorLogsBool=False, inCaptureBool=False)
else:
Orchestrator.OSCMD(inCMDStr=cmd_str,inRunAsyncBool=True)
# ФУНКЦИЯ ОСТАНОВКИ ПРОЦЕССА
@app.post(path=f"/{robot_key_str}/action/stop",tags=[f"{robot_key_str}"])
def robot_stop():
cmd_str = f"taskkill /f /FI \"USERNAME eq %username%\" /im {robot_process_wo_exe_str}.exe"
global robot_is_started_bool
if agent_hostname_str:
Orchestrator.AgentOSCMD(inHostNameStr=agent_hostname_str, inUserStr=agent_username_str,
inCMDStr=cmd_str, inRunAsyncBool=True,
inSendOutputToOrchestratorLogsBool=False, inCaptureBool=False)
else:
Orchestrator.OSCMD(inCMDStr=cmd_str,inRunAsyncBool=True)
# ИНИЦИАЛИЗАЦИЯ ПАНЕЛИ УПРАВЛЕНИЯ РОБОТОМ
cp_manager = Orchestrator.Managers.ControlPanel(inControlPanelNameStr=robot_key_str,
inRefreshHTMLJinja2TemplatePathStr=os.path.join(cp_path_str, "index.html"), inJinja2TemplateRefreshBool = True)
cp_manager.Jinja2DataUpdateDictSet(inJinja2DataUpdateDict={"config": sys.modules[__name__]} )
# HOTFIX Orchestrator.AgentProcessWOExeUpperUserListGet
def HOTFIX_01(inHostNameStr, inUserStr, inGSettings = None):
"""L-,W+: Получить список процессов, которые выполняется на сессии Агента. Все процессы фиксируются без постфикса .exe, а также в верхнем регистре.
ПРИМЕР РЕЗУЛЬТАТА, КОТОРЫЙ МОЖНО ПОЛУЧИТЬ ПО ГУИД ЧЕРЕЗ ФУНКЦИЮ AgentActivityItemReturnGet: ["ORCHESTRATOR", "AGENT", "CHROME", "EXPLORER", ...]
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inHostNameStr: Наименование хоста, на котором запущен Агент. Наименования подключенных агентов доступно для просмотра в панели управления
:param inUserStr: Наименование пользователя, на графической сессии которого запущен Агент. Наименования подключенных агентов доступно для просмотра в панели управления
:return: ГУИД (GUID) строка Активности (ActivityItem). Далее можно ожидать результат этой функции по ГУИД с помощью функции AgentActivityItemReturnGet
"""
inGSettings = Orchestrator.GSettingsGet(inGSettings=inGSettings) # Set the global settings
lActivityItemDict = {
"Def":"ProcessWOExeUpperUserListGet", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"])
"ArgList":[], # Args list
"ArgDict":{}, # Args dictionary
"ArgGSettings": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
#Send item in AgentDict for the futher data transmition
return Orchestrator.AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict)
Orchestrator.AgentProcessWOExeUpperUserListGet = HOTFIX_01

@ -0,0 +1,129 @@
<div class="card" style="width:100%;" id="{{config.robot_key_str}}-card">
<div class="content">
<h1 class="ui center aligned icon header">SIMPLE_01: ПРОСТОЙ РОБОТ</h1>
<div class="ui message">
<div class="header">Применение:</div>
<p>Текст панели управления.</p>
<div class="header">Сотрудник:</div>
{% if UserInfoDict['UserNameUpperStr'] %}
<p>{{UserInfoDict['UserNameUpperStr']}}</p>
{% else %}
<p>Авторизация отключена в настройках оркестратора</p>
{% endif %}
</div>
<div class="ui segment" id="{{config.robot_key_str}}-segment">
<div class="ui two column very relaxed grid">
<div class="column">
<h3 class="ui center aligned icon header">СОСТОЯНИЕ</h3>
{% if config.robot_is_started_bool %}
<h2 class="ui center aligned icon header green">
<i class="circular play icon"></i>
Включен
</h2>
{% else %}
<h2 class="ui center aligned icon header red">
<i class="circular stop icon"></i>
Выключен
</h2>
{% endif %}
</div>
<div class="column">
<h3 class="ui center aligned icon header">ДЕЙСТВИЯ</h3>
<p class="ui center aligned"><button class="ui right labeled icon button" id="{{config.robot_key_str}}-log-btn" onclick="{{config.robot_key_str}}_logs()"><i class="right file alternate icon"></i> Скачать лог </button></p>
<p class="ui center aligned"><button class="ui right labeled icon button" id="{{config.robot_key_str}}-info-btn" onclick="{{config.robot_key_str}}_info()"><i class="right info icon"></i> Скачать инструкцию </button></p>
</div>
</div>
<div class="ui vertical divider">
</div>
</div>
{% if config.robot_is_started_bool %}
<p class="ui center aligned"><button onclick="{{config.robot_key_str}}_action_stop()" id="{{config.robot_key_str}}-action-stop" class="ui fluid right labeled icon button red"><i class="right stop icon"></i> Выключить робота </button></p>
{% else %}
<p class="ui center aligned"><button onclick="{{config.robot_key_str}}_action_start()" id="{{config.robot_key_str}}-action-start" class="ui fluid right labeled icon button green"><i class="right play icon"></i> Включить робота </button></p>
{% endif %}
<h3 class="ui center aligned icon header">ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ</h3>
<div class="ui raised segment" id="{{config.robot_key_str}}-raised-segment">
{% set additional_files = config.additional_files() %}
{% if additional_files|count > 0 %}
{% for item in additional_files %}
<div class="ui label teal">
<i class="file icon"></i> <a href="/{{config.robot_key_str}}/additional/{{item}}" download>{{item}}</a>
</div>
{% endfor %}
{% else %}
<h3>Нет доступных файлов</h3>
{% endif %}
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
// JS CODE GOES HERE
{{config.robot_key_str}}_logs = function () {
var l_now = new Date();
l_month_str = String(l_now.getMonth()+1)
if (l_month_str.length == 1){
l_month_str = "0"+l_month_str
}
l_day_str = String(l_now.getDate())
if (l_day_str.length == 1){
l_day_str = "0"+l_day_str
}
l_log_date_str = String(l_now.getFullYear())+"_"+l_month_str+"_"+l_day_str
l_log_date_str = window.prompt("Укажите дату лог файла для скачивания (формат: гггг_мм_дд)", l_log_date_str);
if (l_log_date_str != null) {
l_url_str = "/{{config.robot_key_str}}/logs/"+l_log_date_str+".log"
var link = document.createElement('a');
link.setAttribute('href', l_url_str);
link.setAttribute('download', l_log_date_str+".log");
link.click();
}
}
{{config.robot_key_str}}_info = function () {
l_url_str = "{{config.howto_url_str}}"
var link = document.createElement('a');
link.setAttribute('href', l_url_str);
link.click();
}
{{config.robot_key_str}}_action_start = function () {
$("#{{config.robot_key_str}}-action-start")[0].disabled=true
$.ajax({
type: "POST",
url: "/{{config.robot_key_str}}/action/start",
success:
function(in_settings)
{},
dataType:'json'
});
}
{{config.robot_key_str}}_action_stop = function () {
$("#{{config.robot_key_str}}-action-stop")[0].disabled=true
$.ajax({
type: "POST",
url: "/{{config.robot_key_str}}/action/stop",
success:
function(in_settings)
{},
dataType:'json'
});
}
});
</script>

@ -60,7 +60,7 @@ html = f'''<!doctype html>
<body>
<div class="card" style="width:450px;" id="card1">
<div class="card" style="width:550px;" id="card1">
<div class="content">
<center><h1>{title}</h1></center><br>
<div class="ui message">

@ -10,7 +10,7 @@
<body>
<div class="card" style="width:450px;" id="card1">
<div class="card" style="width:550px;" id="card1">
<div class="content">
<center><h1>КАЗНАЧЕЙ</h1></center><br>
<div class="ui message">

@ -1,52 +0,0 @@
# Role model - if len of keys in dict is 0 - all access. If at least len is 1 - only this access
# "Orchestrator":{
# "Controls": {
# "RestartOrchestrator": {}, # Feature to restart orchestrator on virtual machine
# "LookMachineScreenshots": {} # Feature to look machina screenshots
# },
# "RDPActive": { # Robot RDP active module
# "ListRead": {} # Access to read RDP session list
# "RestartPC": {} # Restart PC
# "GITRestartOrchestrator": {} # Update GIT + restart orchestrator
# }
# }
# }
# USAGE in .py
# inRequest.
# inRequest.OpenRPA["DefUserRoleAccessAsk"](["Orchestrator","RDPActive","Controls"]) - return True or False
# inRequest.OpenRPA["DefUserRoleHierarchyGet"]() - Return dict of the role hierarchy or {}
# Init Section
gUserNameStr = "Login" # User name without domain name
gDomainNameStr = "" # DOMAIN or EMPTY str if no domain
gDomainIsDefaultBool = True # If domain is exist and is default (default = you can type login without domain name)
def SettingsUpdate(inDict):
lRuleDomainUserDict = {
"MethodMatchURLBeforeList": [
{
"Method":"GET",
"MatchType":"Beginwith",
"URL":"/",
#"FlagAccessDefRequestGlobalAuthenticate": TestDef
"FlagAccess": True
},
{
"Method":"POST",
"MatchType":"Beginwith",
"URL":"/",
#"FlagAccessDefRequestGlobalAuthenticate": TestDef
"FlagAccess": True
}
],
"ControlPanelKeyAllowedList": ["TestControlPanel", "RobotRDPActive","RobotScreenActive", "ControlPanel_Template"], # If empty - all is allowed
"RoleHierarchyAllowedDict": {"Key1":"Test"}
}
# Case add domain + user
inDict["Server"]["AccessUsers"]["RuleDomainUserDict"].update({(gDomainNameStr.upper(),gUserNameStr.upper()):lRuleDomainUserDict})
if gDomainIsDefaultBool:
# Case add default domain + user
inDict["Server"]["AccessUsers"]["RuleDomainUserDict"].update({("",gUserNameStr.upper()):lRuleDomainUserDict})
#Return current dict
return inDict

@ -44,7 +44,7 @@ else:
Orchestrator.OrchestratorLoggerGet().setLevel(logging.INFO)
# TEST Add User ND - Add Login ND to superuser of the Orchestrator
lUACClientDict = SettingsTemplate.__UACClientAdminCreate__()
gSettings["ServerDict"]["AccessUsers"]["FlagCredentialsAsk"]=False
gSettings["ServerDict"]["AccessUsers"]["FlagCredentialsAsk"]=True
Orchestrator.UACUpdate(inGSettings=gSettings, inADLoginStr="ND", inADStr="", inADIsDefaultBool=True, inURLList=[], inRoleHierarchyAllowedDict=lUACClientDict)
Orchestrator.UACUpdate(inGSettings=gSettings, inADLoginStr="rpa00", inADStr="", inADIsDefaultBool=True, inURLList=[], inRoleHierarchyAllowedDict=lUACClientDict)
# TEST Add User IMaslov - Add Login IMaslov to superuser of the Orchestrator
@ -59,8 +59,10 @@ else:
# Restore DUMP
Orchestrator.OrchestratorSessionRestore(inGSettings=gSettings)
# Autoinit control panels starts with CP_
lPyModules = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="Demo\\*\\config.py", inAsyncInitBool=True)
# Call the orchestrator def
lPyModules = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="Demo\\*\\config.py", inAsyncInitBool=True, inPackageLevelInt=1)
#lPyModules2 = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="..\\..\\KPI_Effect\\packages\\*_control_panel\\config.py", inAsyncInitBool=True, inPackageLevelInt=1)
# Call the orchestrator def
Orchestrator.Orchestrator(inGSettings=gSettings, inDumpRestoreBool=False)

@ -1,24 +0,0 @@
chcp 65001
@echo off
echo Формат использования init-python-env.cmd [имя запускаемого процесса.exe] [имя убиваемого процесса.exe]
echo Пример использования init-python-env.cmd orpa-rbt.exe orpa-rbt.exe
if [%1]==[] goto :python-env
goto create-exe
:create-exe
copy /Y "%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe" "%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64\%1"
if [%2]==[] goto :python-env
goto taskkill
:taskkill
taskkill /im "%2" /F /fi "username eq %username%"
goto :python-env
:python-env
set CD_PREV=%cd%
cd /d "%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64"
set PATH=%cd%;%cd%\Scripts;%PATH%
cd /d "%~dp0..\Sources"
set PYTHONPATH=%cd%;%PYTHONPATH%
cd %CD_PREV%
:eof
echo Инициализация Python окружения прошла успешно!
@echo on

@ -1,4 +0,0 @@
chcp 65001
cd /d "%~dp0"
call init-python-env.cmd orpa-rbt.exe orpa-rbt.exe
cmd

@ -1,5 +1,4 @@
chcp 65001
cd /d "%~dp0"
call init-python-env.cmd orpa-orc.exe orpa-orc.exe
orpa-orc.exe "config.py"
pause>nul
call .\..\Scripts\PythonWinInit.cmd script orpa-orc.exe orpa-orc.exe
orpa-orc.exe "config.py"

@ -0,0 +1,15 @@
# This is the canonical package information.
__author__ = 'Andrew Dunham'
__license__ = 'Apache'
__copyright__ = "Copyright (c) 2012-2013, Andrew Dunham"
__version__ = "0.0.6"
from .multipart import (
FormParser,
MultipartParser,
QuerystringParser,
OctetStreamParser,
create_form_parser,
parse_form,
)

@ -0,0 +1,171 @@
import base64
import binascii
from .exceptions import DecodeError
class Base64Decoder:
"""This object provides an interface to decode a stream of Base64 data. It
is instantiated with an "underlying object", and whenever a write()
operation is performed, it will decode the incoming data as Base64, and
call write() on the underlying object. This is primarily used for decoding
form data encoded as Base64, but can be used for other purposes::
from multipart.decoders import Base64Decoder
fd = open("notb64.txt", "wb")
decoder = Base64Decoder(fd)
try:
decoder.write("Zm9vYmFy") # "foobar" in Base64
decoder.finalize()
finally:
decoder.close()
# The contents of "notb64.txt" should be "foobar".
This object will also pass all finalize() and close() calls to the
underlying object, if the underlying object supports them.
Note that this class maintains a cache of base64 chunks, so that a write of
arbitrary size can be performed. You must call :meth:`finalize` on this
object after all writes are completed to ensure that all data is flushed
to the underlying object.
:param underlying: the underlying object to pass writes to
"""
def __init__(self, underlying):
self.cache = bytearray()
self.underlying = underlying
def write(self, data):
"""Takes any input data provided, decodes it as base64, and passes it
on to the underlying object. If the data provided is invalid base64
data, then this method will raise
a :class:`multipart.exceptions.DecodeError`
:param data: base64 data to decode
"""
# Prepend any cache info to our data.
if len(self.cache) > 0:
data = self.cache + data
# Slice off a string that's a multiple of 4.
decode_len = (len(data) // 4) * 4
val = data[:decode_len]
# Decode and write, if we have any.
if len(val) > 0:
try:
decoded = base64.b64decode(val)
except binascii.Error:
raise DecodeError('There was an error raised while decoding '
'base64-encoded data.')
self.underlying.write(decoded)
# Get the remaining bytes and save in our cache.
remaining_len = len(data) % 4
if remaining_len > 0:
self.cache = data[-remaining_len:]
else:
self.cache = b''
# Return the length of the data to indicate no error.
return len(data)
def close(self):
"""Close this decoder. If the underlying object has a `close()`
method, this function will call it.
"""
if hasattr(self.underlying, 'close'):
self.underlying.close()
def finalize(self):
"""Finalize this object. This should be called when no more data
should be written to the stream. This function can raise a
:class:`multipart.exceptions.DecodeError` if there is some remaining
data in the cache.
If the underlying object has a `finalize()` method, this function will
call it.
"""
if len(self.cache) > 0:
raise DecodeError('There are %d bytes remaining in the '
'Base64Decoder cache when finalize() is called'
% len(self.cache))
if hasattr(self.underlying, 'finalize'):
self.underlying.finalize()
def __repr__(self):
return f"{self.__class__.__name__}(underlying={self.underlying!r})"
class QuotedPrintableDecoder:
"""This object provides an interface to decode a stream of quoted-printable
data. It is instantiated with an "underlying object", in the same manner
as the :class:`multipart.decoders.Base64Decoder` class. This class behaves
in exactly the same way, including maintaining a cache of quoted-printable
chunks.
:param underlying: the underlying object to pass writes to
"""
def __init__(self, underlying):
self.cache = b''
self.underlying = underlying
def write(self, data):
"""Takes any input data provided, decodes it as quoted-printable, and
passes it on to the underlying object.
:param data: quoted-printable data to decode
"""
# Prepend any cache info to our data.
if len(self.cache) > 0:
data = self.cache + data
# If the last 2 characters have an '=' sign in it, then we won't be
# able to decode the encoded value and we'll need to save it for the
# next decoding step.
if data[-2:].find(b'=') != -1:
enc, rest = data[:-2], data[-2:]
else:
enc = data
rest = b''
# Encode and write, if we have data.
if len(enc) > 0:
self.underlying.write(binascii.a2b_qp(enc))
# Save remaining in cache.
self.cache = rest
return len(data)
def close(self):
"""Close this decoder. If the underlying object has a `close()`
method, this function will call it.
"""
if hasattr(self.underlying, 'close'):
self.underlying.close()
def finalize(self):
"""Finalize this object. This should be called when no more data
should be written to the stream. This function will not raise any
exceptions, but it may write more data to the underlying object if
there is data remaining in the cache.
If the underlying object has a `finalize()` method, this function will
call it.
"""
# If we have a cache, write and then remove it.
if len(self.cache) > 0:
self.underlying.write(binascii.a2b_qp(self.cache))
self.cache = b''
# Finalize our underlying stream.
if hasattr(self.underlying, 'finalize'):
self.underlying.finalize()
def __repr__(self):
return f"{self.__class__.__name__}(underlying={self.underlying!r})"

@ -0,0 +1,46 @@
class FormParserError(ValueError):
"""Base error class for our form parser."""
pass
class ParseError(FormParserError):
"""This exception (or a subclass) is raised when there is an error while
parsing something.
"""
#: This is the offset in the input data chunk (*NOT* the overall stream) in
#: which the parse error occurred. It will be -1 if not specified.
offset = -1
class MultipartParseError(ParseError):
"""This is a specific error that is raised when the MultipartParser detects
an error while parsing.
"""
pass
class QuerystringParseError(ParseError):
"""This is a specific error that is raised when the QuerystringParser
detects an error while parsing.
"""
pass
class DecodeError(ParseError):
"""This exception is raised when there is a decoding error - for example
with the Base64Decoder or QuotedPrintableDecoder.
"""
pass
# On Python 3.3, IOError is the same as OSError, so we don't want to inherit
# from both of them. We handle this case below.
if IOError is not OSError: # pragma: no cover
class FileError(FormParserError, IOError, OSError):
"""Exception class for problems with the File class."""
pass
else: # pragma: no cover
class FileError(FormParserError, OSError):
"""Exception class for problems with the File class."""
pass

@ -0,0 +1,133 @@
import os
import re
import sys
import types
import functools
def ensure_in_path(path):
"""
Ensure that a given path is in the sys.path array
"""
if not os.path.isdir(path):
raise RuntimeError('Tried to add nonexisting path')
def _samefile(x, y):
try:
return os.path.samefile(x, y)
except OSError:
return False
except AttributeError:
# Probably on Windows.
path1 = os.path.abspath(x).lower()
path2 = os.path.abspath(y).lower()
return path1 == path2
# Remove existing copies of it.
for pth in sys.path:
if _samefile(pth, path):
sys.path.remove(pth)
# Add it at the beginning.
sys.path.insert(0, path)
# Check if pytest is imported. If so, we use it to create marking decorators.
# If not, we just create a function that does nothing.
try:
import pytest
except ImportError:
pytest = None
if pytest is not None:
slow_test = pytest.mark.slow_test
xfail = pytest.mark.xfail
else:
slow_test = lambda x: x
def xfail(*args, **kwargs):
if len(args) > 0 and isinstance(args[0], types.FunctionType):
return args[0]
return lambda x: x
# We don't use the pytest parametrizing function, since it seems to break
# with unittest.TestCase subclasses.
def parametrize(field_names, field_values):
# If we're not given a list of field names, we make it.
if not isinstance(field_names, (tuple, list)):
field_names = (field_names,)
field_values = [(val,) for val in field_values]
# Create a decorator that saves this list of field names and values on the
# function for later parametrizing.
def decorator(func):
func.__dict__['param_names'] = field_names
func.__dict__['param_values'] = field_values
return func
return decorator
# This is a metaclass that actually performs the parametrization.
class ParametrizingMetaclass(type):
IDENTIFIER_RE = re.compile('[^A-Za-z0-9]')
def __new__(klass, name, bases, attrs):
new_attrs = attrs.copy()
for attr_name, attr in attrs.items():
# We only care about functions
if not isinstance(attr, types.FunctionType):
continue
param_names = attr.__dict__.pop('param_names', None)
param_values = attr.__dict__.pop('param_values', None)
if param_names is None or param_values is None:
continue
# Create multiple copies of the function.
for i, values in enumerate(param_values):
assert len(param_names) == len(values)
# Get a repr of the values, and fix it to be a valid identifier
human = '_'.join(
[klass.IDENTIFIER_RE.sub('', repr(x)) for x in values]
)
# Create a new name.
# new_name = attr.__name__ + "_%d" % i
new_name = attr.__name__ + "__" + human
# Create a replacement function.
def create_new_func(func, names, values):
# Create a kwargs dictionary.
kwargs = dict(zip(names, values))
@functools.wraps(func)
def new_func(self):
return func(self, **kwargs)
# Manually set the name and return the new function.
new_func.__name__ = new_name
return new_func
# Actually create the new function.
new_func = create_new_func(attr, param_names, values)
# Save this new function in our attrs dict.
new_attrs[new_name] = new_func
# Remove the old attribute from our new dictionary.
del new_attrs[attr_name]
# We create the class as normal, except we use our new attributes.
return type.__new__(klass, name, bases, new_attrs)
# This is a class decorator that actually applies the above metaclass.
def parametrize_class(klass):
return ParametrizingMetaclass(klass.__name__,
klass.__bases__,
klass.__dict__)

@ -0,0 +1,5 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content- isposition: form-data; name="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,3 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
error: 51

@ -0,0 +1,5 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; n me="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,13 @@
----boundary
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
--boundari
--boundaryq--boundary q--boundarq
--bounaryd--
--notbound--
--mismatch
--mismatch--
--boundary-Q
--boundary Q--boundaryQ
----boundary--

@ -0,0 +1,8 @@
boundary: --boundary
expected:
- name: file
type: file
file_name: test.txt
data: !!binary |
LS1ib3VuZGFyaQ0KLS1ib3VuZGFyeXEtLWJvdW5kYXJ5DXEtLWJvdW5kYXJxDQotLWJvdW5hcnlkLS0NCi0tbm90Ym91bmQtLQ0KLS1taXNtYXRjaA0KLS1taXNtYXRjaC0tDQotLWJvdW5kYXJ5LVENCi0tYm91bmRhcnkNUS0tYm91bmRhcnlR

@ -0,0 +1,6 @@
----boundary
Content-Disposition: form-data; name="field"
QQQQQQQQQQQQQQQQQQQQ
----boundaryQQQQQQQQQQQQQQQQQQQQ
----boundary--

@ -0,0 +1,8 @@
boundary: --boundary
expected:
- name: field
type: field
data: !!binary |
UVFRUVFRUVFRUVFRUVFRUVFRUVENCi0tLS1ib3VuZGFyeVFRUVFRUVFRUVFRUVFRUVFRUVFR

@ -0,0 +1,6 @@
----boundary
Content-Disposition: form-data; name="field"
QQQQQQQQQQQQQQQQQQQQ
----boundary QQQQQQQQQQQQQQQQQQQQ
----boundary--

@ -0,0 +1,8 @@
boundary: --boundary
expected:
- name: field
type: field
data: !!binary |
UVFRUVFRUVFRUVFRUVFRUVFRUVENCi0tLS1ib3VuZGFyeQ1RUVFRUVFRUVFRUVFRUVFRUVFRUQ==

@ -0,0 +1,6 @@
----boundary
Content-Disposition: form-data; name="field"
QQQQQQQQQQQQQQQQQQQQ
----boundary-QQQQQQQQQQQQQQQQQQQQ
----boundary--

@ -0,0 +1,8 @@
boundary: --boundary
expected:
- name: field
type: field
data: !!binary |
UVFRUVFRUVFRUVFRUVFRUVFRUVENCi0tLS1ib3VuZGFyeS1RUVFRUVFRUVFRUVFRUVFRUVFRUQ==

@ -0,0 +1,4 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field"
QThis is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,5 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-999position: form-data; name="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,3 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
error: 50

@ -0,0 +1,5 @@
------WebQitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,7 @@
----boundary
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
Content-Transfer-Encoding: base64
VGVzdCAxMjM=
----boundary--

@ -0,0 +1,7 @@
boundary: --boundary
expected:
- name: file
type: file
file_name: test.txt
data: !!binary |
VGVzdCAxMjM=

@ -0,0 +1,5 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
: form-data; name="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,3 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
error: 42

@ -0,0 +1,9 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field1"
field1
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field2"
field2
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,10 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
- name: field1
type: field
data: !!binary |
ZmllbGQx
- name: field2
type: field
data: !!binary |
ZmllbGQy

@ -0,0 +1,11 @@
------WebKitFormBoundarygbACTUR58IyeurVf
Content-Disposition: form-data; name="file1"; filename="test1.txt"
Content-Type: text/plain
Test file #1
------WebKitFormBoundarygbACTUR58IyeurVf
Content-Disposition: form-data; name="file2"; filename="test2.txt"
Content-Type: text/plain
Test file #2
------WebKitFormBoundarygbACTUR58IyeurVf--

@ -0,0 +1,13 @@
boundary: ----WebKitFormBoundarygbACTUR58IyeurVf
expected:
- name: file1
type: file
file_name: test1.txt
data: !!binary |
VGVzdCBmaWxlICMx
- name: file2
type: file
file_name: test2.txt
data: !!binary |
VGVzdCBmaWxlICMy

@ -0,0 +1,7 @@
----boundary
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable
foo=3Dbar
----boundary--

@ -0,0 +1,7 @@
boundary: --boundary
expected:
- name: file
type: file
file_name: test.txt
data: !!binary |
Zm9vPWJhcg==

@ -0,0 +1,5 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,6 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
- name: field
type: field
data: !!binary |
VGhpcyBpcyBhIHRlc3Qu

@ -0,0 +1,5 @@
--boundary
Content-Disposition: form-data; name="field"
0123456789ABCDEFGHIJ0123456789ABCDEFGHIJ
--boundary--

@ -0,0 +1,6 @@
boundary: --boundary
expected:
- name: field
type: field
data: !!binary |
MDEyMzQ1Njc4OUFCQ0RFRkdISUowMTIzNDU2Nzg5QUJDREVGR0hJSg==

@ -0,0 +1,5 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field"
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,6 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
- name: field
type: field
data: !!binary |
cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXE=

@ -0,0 +1,10 @@
--boundary
Content-Disposition: form-data; name="field"
test1
--boundary
Content-Disposition: form-data; name="file"; filename="file.txt"
Content-Type: text/plain
test2
--boundary--

@ -0,0 +1,13 @@
boundary: boundary
expected:
- name: field
type: field
data: !!binary |
dGVzdDE=
- name: file
type: file
file_name: file.txt
data: !!binary |
dGVzdDI=

@ -0,0 +1,7 @@
------WebKitFormBoundaryTkr3kCBQlBe1nrhc
Content-Disposition: form-data; name="field"
This is a test.
------WebKitFormBoundaryTkr3kCBQlBe1nrhc--

@ -0,0 +1,6 @@
boundary: ----WebKitFormBoundaryTkr3kCBQlBe1nrhc
expected:
- name: field
type: field
data: !!binary |
VGhpcyBpcyBhIHRlc3Qu

@ -0,0 +1,6 @@
------WebKitFormBoundary5BZGOJCWtXGYC9HW
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
This is a test file.
------WebKitFormBoundary5BZGOJCWtXGYC9HW--

@ -0,0 +1,8 @@
boundary: ----WebKitFormBoundary5BZGOJCWtXGYC9HW
expected:
- name: file
type: file
file_name: test.txt
data: !!binary |
VGhpcyBpcyBhIHRlc3QgZmlsZS4=

@ -0,0 +1,6 @@
------WebKitFormBoundaryI9SCEFp2lpx5DR2K
Content-Disposition: form-data; name="file"; filename="???.txt"
Content-Type: text/plain
これはテストです。
------WebKitFormBoundaryI9SCEFp2lpx5DR2K--

@ -0,0 +1,8 @@
boundary: ----WebKitFormBoundaryI9SCEFp2lpx5DR2K
expected:
- name: file
type: file
file_name: ???.txt
data: !!binary |
44GT44KM44Gv44OG44K544OI44Gn44GZ44CC

@ -0,0 +1,69 @@
Metadata-Version: 2.1
Name: python-multipart
Version: 0.0.6
Summary: A streaming multipart parser for Python
Project-URL: Homepage, https://github.com/andrew-d/python-multipart
Project-URL: Documentation, https://andrew-d.github.io/python-multipart/
Project-URL: Changelog, https://github.com/andrew-d/python-multipart/tags
Project-URL: Source, https://github.com/andrew-d/python-multipart
Author-email: Andrew Dunham <andrew@du.nham.ca>
License-Expression: Apache-2.0
License-File: LICENSE.txt
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.7
Provides-Extra: dev
Requires-Dist: atomicwrites==1.2.1; extra == 'dev'
Requires-Dist: attrs==19.2.0; extra == 'dev'
Requires-Dist: coverage==6.5.0; extra == 'dev'
Requires-Dist: hatch; extra == 'dev'
Requires-Dist: invoke==1.7.3; extra == 'dev'
Requires-Dist: more-itertools==4.3.0; extra == 'dev'
Requires-Dist: pbr==4.3.0; extra == 'dev'
Requires-Dist: pluggy==1.0.0; extra == 'dev'
Requires-Dist: py==1.11.0; extra == 'dev'
Requires-Dist: pytest-cov==4.0.0; extra == 'dev'
Requires-Dist: pytest-timeout==2.1.0; extra == 'dev'
Requires-Dist: pytest==7.2.0; extra == 'dev'
Requires-Dist: pyyaml==5.1; extra == 'dev'
Description-Content-Type: text/x-rst
==================
Python-Multipart
==================
.. image:: https://github.com/andrew-d/python-multipart/actions/workflows/test.yaml/badge.svg
:target: https://github.com/andrew-d/python-multipart/actions
python-multipart is an Apache2 licensed streaming multipart parser for Python.
Test coverage is currently 100%.
Documentation is available `here`_.
.. _here: https://andrew-d.github.io/python-multipart/
Why?
----
Because streaming uploads are awesome for large files.
How to Test
-----------
If you want to test:
.. code-block:: bash
$ pip install .[dev]
$ inv test

@ -0,0 +1,62 @@
multipart/__init__.py,sha256=EaZd7hXXXNz5RWfzZ4lr-wKWXC4anMNWE7u4tPXtWr0,335
multipart/__pycache__/__init__.cpython-37.pyc,,
multipart/__pycache__/decoders.cpython-37.pyc,,
multipart/__pycache__/exceptions.cpython-37.pyc,,
multipart/__pycache__/multipart.cpython-37.pyc,,
multipart/decoders.py,sha256=6LeCVARmDrQgmMsaul1WUIf79Q-mLE9swhGxumQe_98,6107
multipart/exceptions.py,sha256=yDZ9pqq3Y9ZMCvj2TkAvOcNdMjFHjLnHl4luFnzt750,1410
multipart/multipart.py,sha256=ZRc1beZCgCIXkYe0Xwxh_g4nFdrp3eEid4XODYIfqgQ,71230
multipart/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
multipart/tests/__pycache__/__init__.cpython-37.pyc,,
multipart/tests/__pycache__/compat.cpython-37.pyc,,
multipart/tests/__pycache__/test_multipart.cpython-37.pyc,,
multipart/tests/compat.py,sha256=3aowcimO1SYU6WqS3GlUJ3jmkgLH63e8AsUPjlta1xU,4266
multipart/tests/test_data/http/CR_in_header.http,sha256=XEimN_BgEqQXCqK463bMgD9PKIQeLrQhWt2M3vNr9cE,149
multipart/tests/test_data/http/CR_in_header.yaml,sha256=OEzE2PqK78fi9kjM23YOu4xM0zQ_LRwSiwqFNAmku50,73
multipart/tests/test_data/http/CR_in_header_value.http,sha256=pf4sP-l4_hzZ8Kr51gUE6CFcCifuWSZ10-vnx6mtXDg,149
multipart/tests/test_data/http/CR_in_header_value.yaml,sha256=WjqJNYL-cUH2n9k-Xdy1YDvSfDqqXxsiinBDn3HTUu4,73
multipart/tests/test_data/http/almost_match_boundary.http,sha256=jIsp1M6BHQIHF9o965z3Pt8TFncVvaBj5N43hprRpBM,264
multipart/tests/test_data/http/almost_match_boundary.yaml,sha256=Hr7WZBwZrbf4vjurjRzGGeY9tFVJLRRmV1rEFXop-6s,300
multipart/tests/test_data/http/almost_match_boundary_without_CR.http,sha256=KviMqo_FUy1N1-b-YUfyWhs5PmN6_fU7qhMYFTGnUhI,132
multipart/tests/test_data/http/almost_match_boundary_without_CR.yaml,sha256=HjlUni-nuX3bG2-3FILo4GLBpLD4DImQ48VPlfnfIWY,167
multipart/tests/test_data/http/almost_match_boundary_without_LF.http,sha256=KylmJ0O-RfnUnXbjVhwJpzHsWqNTPJn29_wfsvrG7AM,133
multipart/tests/test_data/http/almost_match_boundary_without_LF.yaml,sha256=tkzz_kOFZtkarmMnTen355nm8McPwbmPmWGMxUUBSzU,171
multipart/tests/test_data/http/almost_match_boundary_without_final_hyphen.http,sha256=L6bzRistD4X5TTd1zBtfR6gM4EQL77_iBI_Pgaw4ufw,133
multipart/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml,sha256=cFKxwFMYTo9PKRb04Iai__mY9KG29IPkSm3p80DgEZw,171
multipart/tests/test_data/http/bad_end_of_headers.http,sha256=ucEDylTCg1_hdEVkIc-1k8ZQ-CBIf5uXfDKbSBsSaF0,149
multipart/tests/test_data/http/bad_end_of_headers.yaml,sha256=1UHERY2D7tp0HEUl5xD4SiotP2skETmBOF5EjcG2HTw,73
multipart/tests/test_data/http/bad_header_char.http,sha256=zTqXFNQ9yrbc82vubPg95T4edg1Ueh2xadlVD2lO51A,149
multipart/tests/test_data/http/bad_header_char.yaml,sha256=9ykVsASnvYvX51qtkCJqhgegeN-hoSU40MsYQvqeVNo,73
multipart/tests/test_data/http/bad_initial_boundary.http,sha256=IGFSkpmw21XfAXr0xOHwj0vnhxyj-uCWVjcljo68LLo,149
multipart/tests/test_data/http/bad_initial_boundary.yaml,sha256=eBSbue0BYDYhYtKdBCnm1LGq0O_fOMwV6ZoLpZFDFM4,72
multipart/tests/test_data/http/base64_encoding.http,sha256=fDbr4BgLdNS8kYiTO7g4HxB81hvmiD2sRUCAoijfRx0,173
multipart/tests/test_data/http/base64_encoding.yaml,sha256=cz2KxZxoi81MiXRh7DmJQOWcdqQH5ahkrJydGYv4hpU,125
multipart/tests/test_data/http/empty_header.http,sha256=-wSHHSLu1D2wfdC8Zcaw5TX_USTvWz56CANpsceOZYQ,130
multipart/tests/test_data/http/empty_header.yaml,sha256=4xdVCYJ-l88HMXkMLNkSQoLNgURoGcKzR1AclPLpkOc,73
multipart/tests/test_data/http/multiple_fields.http,sha256=6p93ls_B7bk8mXPYhsrFwvktSX8CuRdUH4vn-EZBaRM,242
multipart/tests/test_data/http/multiple_fields.yaml,sha256=mePM5DVfAzty7QNEEyMu2qrFI28TbG9yWRvWFpWj7Jo,197
multipart/tests/test_data/http/multiple_files.http,sha256=EtmagVBVpsFGnCqlwfKgswQfU8lGa3QNkP6GVJBa5A0,348
multipart/tests/test_data/http/multiple_files.yaml,sha256=QO9JMgTvkL2EmIWAl8LcbDrkfNmDk0eA5SOk3gFuFWE,260
multipart/tests/test_data/http/quoted_printable_encoding.http,sha256=--yYceg17SmqIJsazw-SFChdxeTAq8zV4lzPVM_QMrM,180
multipart/tests/test_data/http/quoted_printable_encoding.yaml,sha256=G_L6lnP-e4uHfGpYQFopxDdpbd_EbxL2oY8N910BTOI,127
multipart/tests/test_data/http/single_field.http,sha256=JjdSwFiM0mG07HYzBCcjzeqgqAA9glx-VcRUjkOh8cA,149
multipart/tests/test_data/http/single_field.yaml,sha256=HMXd14-m9sKBvTsnzWOaG12_3wve5SoXeUISF93wlRc,139
multipart/tests/test_data/http/single_field_blocks.http,sha256=4laZAIbFmxERZtgPWzuOihvEhLWD1NGTSdqZ6Ra58Ns,115
multipart/tests/test_data/http/single_field_blocks.yaml,sha256=6mKvHtmiXh6OxoibJsx5pUreIMyQyPb_DWy7GEG9BX8,147
multipart/tests/test_data/http/single_field_longer.http,sha256=BTBt1MsUaxuHauu-mljb3lU-8Z2dpjRN_lkZW4pkDXA,262
multipart/tests/test_data/http/single_field_longer.yaml,sha256=aENhQPtHaTPIvgJbdiDHvcOtcthEEUHCQIEfLj0aalY,293
multipart/tests/test_data/http/single_field_single_file.http,sha256=G4dV0iCSjvEk5DSJ1VXWy6R8Hon3-WOExep41nPWVeQ,192
multipart/tests/test_data/http/single_field_single_file.yaml,sha256=QO9gqdXQsoizLji9r8kdlPWHJB5vO7wszqP1fHvsNV8,189
multipart/tests/test_data/http/single_field_with_leading_newlines.http,sha256=YfNEUdZxbi4bBGTU4T4WSQZ6QJDJlcLZUczYzGU5Jaw,153
multipart/tests/test_data/http/single_field_with_leading_newlines.yaml,sha256=HMXd14-m9sKBvTsnzWOaG12_3wve5SoXeUISF93wlRc,139
multipart/tests/test_data/http/single_file.http,sha256=axRB0Keb4uhAfHxt7Na1x9-PQHCiiKK8s38a2GG860E,202
multipart/tests/test_data/http/single_file.yaml,sha256=eUKyGkNTDrXdGni4EyEDbxDBTfAKsstVQ5O5SWghYTc,170
multipart/tests/test_data/http/utf8_filename.http,sha256=w_Ryf4hC_KJo7v-a18dJFECqm21nzA5Z18dsGyu6zjA,208
multipart/tests/test_data/http/utf8_filename.yaml,sha256=KpDc4e-yYp_JUXa-S5lp591tzoEybgywtGian0kQFPc,177
multipart/tests/test_multipart.py,sha256=VrxoOtXO4NWpT1OJqo7FWWIybnxGReumIWCR-FDIHCk,38988
python_multipart-0.0.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
python_multipart-0.0.6.dist-info/METADATA,sha256=J4WQf99XHSSg_EDG7fGgJGotS_Hp7ViCtpY4rQ2OgyM,2459
python_multipart-0.0.6.dist-info/RECORD,,
python_multipart-0.0.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
python_multipart-0.0.6.dist-info/WHEEL,sha256=Fd6mP6ydyRguakwUJ05oBE7fh2IPxgtDN9IwHJ9OqJQ,87
python_multipart-0.0.6.dist-info/licenses/LICENSE.txt,sha256=qOgzF2zWF9rwC51tOfoVyo7evG0WQwec0vSJPAwom-I,556

@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: hatchling 1.13.0
Root-Is-Purelib: true
Tag: py3-none-any

@ -0,0 +1,14 @@
Copyright 2012, Andrew Dunham
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,31 @@
& separates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatenate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SetLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.

@ -0,0 +1,75 @@
chcp 65001
if [%1]==[echo_on] goto :program
:echo_no
set TEMP_ECHO_NO=TRUE
@echo off
:program
if NOT "%PYTHON_CONFIGURE%"=="" goto :echo_check
set PYTHON_CONFIGURE=%~f0
set CD_PREV=%cd%
rem "%~dp0..\Resources" - УСТАНОВКА ПУТИ ОТ РАСПОЛОЖЕНИЯ ФАЙЛА, В КОТОРОМ НАПИСАН ЭТОТ ТЕКСТ. ВНИМАНИЕ! ПОСЛЕ %~dp0 СИМВОЛ \ СТАВИТЬ НЕ ТРЕБУЕТСЯ
rem "%cd%\..\Resources" - УСТАНОВКА ПУТИ ОТ РАБОЧЕЙ ДИРЕКТОРИИ, В КОТОРОЙ БЫЛ ВЫЗВАН ЭТОТ СКРИПТ. ВНИМАНИЕ! ПОСЛЕ %cd% СИМВОЛ \ СТАВИТЬ ТРЕБУЕТСЯ ОБЯЗАТЕЛЬНО
rem Пример использования python-win-configure.cmd python - выполнить команду python в настроенном окружении
goto comment_set_path
rem ПРИМЕР КОНСТРУКЦИИ ДЛЯ ДОБАВЛЕНИЯ ДИРЕКТОРИИ В ПЕРЕМЕННУЮ ОКРУЖЕНИЯ PATH
set TEMP_LOCATION="%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64"
cd /d %TEMP_LOCATION%
set PATH=%cd%;%PATH%
rem ВНИМАНИЕ! ЗНАК = ДОЛЖЕН БЫТЬ УСТАНОВЛЕН ВПРИТЫК С ПЕРЕМЕННОЙ ОКРУЖЕНИЯ И ЗНАЧЕНИЕМ - ИНАЧЕ УСТАНОВКА ЗНАЧЕНИЯ БУДЕТ ПРОИЗВЕДЕНА НЕКОРРЕКТНО
:comment_set_path
goto comment_set_pythonpath
rem ПРИМЕР КОНСТРУКЦИИ ДЛЯ ДОБАВЛЕНИЯ ДИРЕКТОРИИ В ПЕРЕМЕННУЮ ОКРУЖЕНИЯ PYTHONPATH
set TEMP_LOCATION="%~dp0..\Sources"
cd /d %TEMP_LOCATION%
set PYTHONPATH=%cd%;%PYTHONPATH%
rem ВНИМАНИЕ! ЗНАК = ДОЛЖЕН БЫТЬ УСТАНОВЛЕН ВПРИТЫК С ПЕРЕМЕННОЙ ОКРУЖЕНИЯ И ЗНАЧЕНИЕМ - ИНАЧЕ УСТАНОВКА ЗНАЧЕНИЯ БУДЕТ ПРОИЗВЕДЕНА НЕКОРРЕКТНО
:comment_set_pythonpath
rem ЗОНА УСТАНОВКИ ПЕРЕМЕННЫХ ОКРУЖЕНИЯ!
:configure
set TEMP_LOCATION="%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64\Scripts"
cd /d %TEMP_LOCATION%
set PATH=%cd%;%PATH%
set TEMP_LOCATION="%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64"
cd /d %TEMP_LOCATION%
set PATH=%cd%;%PATH%
set TEMP_LOCATION="%~dp0..\Sources"
cd /d %TEMP_LOCATION%
set PYTHONPATH=%cd%;%PYTHONPATH%
rem восстановление каталога рабочей директории программы
cd /d %CD_PREV%
rem выполнить вызов, если есть параметры, начиная с %2+
if "%1"=="" goto :echo_check
set ORPA_ARGV=
rem один shift - все параметры, начиная с %2 (или с %1, если не echo_on)
if "%1"=="echo_on" shift
:loop1
if "%1"=="" goto after_loop
if "%ORPA_ARGV%"=="" goto :no_param
set ORPA_ARGV=%ORPA_ARGV% %1
goto :loop1_next
:no_param
set ORPA_ARGV=%1
:loop1_next
shift
goto loop1
:after_loop
echo PYTHON-CONFIGURE: ОБНАРУЖЕНЫ ПАРАМЕТРЫ ИНИЦИАЛИЗАЦИИ ПРОГРАММЫ: %ORPA_ARGV%
:echo_check
rem включить Echo, если он был принудительно выключен
if NOT [%TEMP_ECHO_NO%]==[] goto :echo_on
goto :eof
:echo_on
@echo on
:eof
if "%ORPA_ARGV%"=="" goto :eof2
%ORPA_ARGV%
pause>nul
:eof2

@ -0,0 +1,24 @@
@echo off
chcp 65001
echo Формат использования PythonWinInit.cmd [console,script] [имя запускаемого процесса.exe] [имя убиваемого процесса.exe]
echo Пример использования PythonWinInit.cmd script orpa-rbt.exe orpa-rbt.exe
if [%2]==[] goto :python-env
goto create-exe
:create-exe
copy /Y "%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe" "%~dp0..\Resources\WPy64-3720\python-3.7.2.amd64\%2"
if [%3]==[] goto :python-env
goto taskkill
:taskkill
taskkill /im "%3" /F /fi "username eq %username%"
goto :python-env
:python-env
call "%~dp0PythonWinConfigure.cmd"
echo Инициализация Python окружения прошла успешно!
if [%1]==[] goto :console
if [%1]==[console] goto :console
goto :eof
:console
cmd
:eof
@echo on

@ -0,0 +1,22 @@
####################################
2. Список изменений
####################################
Ниже представлен список изменений в релизах pyOpenRPA
.. include:: ../../../changelog.md
:literal:
pyOpenRPA - роботы помогут!
******************************
Быстрая навигация
******************************
- `Сообщество pyOpenRPA (telegram) <https://t.me/pyOpenRPA>`_
- `Сообщество pyOpenRPA (tenchat) <https://tenchat.ru/iMaslov?utm_source=19f2a84f-3268-437f-950c-d987ae42af24>`_
- `Сообщество pyOpenRPA (вконтакте) <https://vk.com/pyopenrpa>`_
- `Презентация pyOpenRPA <https://pyopenrpa.ru/Index/pyOpenRPA_product_service.pdf>`_
- `Портал pyOpenRPA <https://pyopenrpa.ru>`_
- `Репозиторий pyOpenRPA <https://gitlab.com/UnicodeLabs/OpenRPA>`_

@ -21,6 +21,31 @@
# Call the orchestrator main def
Orchestrator.Orchestrator(inGSettings=gSettings)
************************************************************
Шаблоны функций веб-сервера (с использованием FastAPI)
************************************************************
.. code-block:: python
# ПРИМЕР Если НЕ требуется авторизация пользователя (получить inAuthTokenStr)
from fastapi import Request
from fastapi.responses import JSONResponse, HTMLResponse
@app.post("/url/to/def",response_class=JSONResponse)
async def some_def(inRequest:Request):
l_input_dict = await inRequest.json()
if lValueStr == None or lValueStr == b"": lValueStr=""
else: lValueStr = lValueStr.decode("utf8")
# ПРИМЕР Если требуется авторизация пользователя (получить inAuthTokenStr)
from fastapi import Request
from fastapi.responses import JSONResponse, HTMLResponse
from pyOpenRPA import Orchestrator
@app.post("/url/to/def",response_class=JSONResponse)
async def some_def(inRequest:Request, inAuthTokenStr:str=Depends(Orchestrator.WebAuthDefGet())):
l_input_dict = await inRequest.json()
if lValueStr == None or lValueStr == b"": lValueStr=""
else: lValueStr = lValueStr.decode("utf8")
******************************
Конфигурационный файл config.py

@ -53,9 +53,9 @@ pyOpenRPA - роботы помогут!
**Для коммерческого использования:**
* **Вариант 1:** Подписка на премиальную техническую поддержку от вендора (ежемесячно от 66 900 руб. без НДС)
* **Вариант 2:** Покупка бессрочной лицензии на текущую версию pyOpenRPA и ниже (разовый платеж 299 000 руб. без НДС)
* **Вариант 3:** Покупка бессрочной лицензии на 1-го робота, который будет использовать текущую версию pyOpenPRA и ниже (разовый платеж 99 000 руб. без НДС)
* **Вариант 1:** Подписка на премиальную техническую поддержку от вендора
* **Вариант 2:** Покупка бессрочной лицензии на текущую версию pyOpenRPA и ниже
* **Вариант 3:** Покупка бессрочной лицензии на 1-го робота, который будет использовать текущую версию pyOpenPRA и ниже
Используя ПО pyOpenRPA Вы осознаете свою ответственность в случаях нарушения лицензионного законодательства и совершения неправомерных действий.
@ -163,8 +163,8 @@ pyOpenRPA - роботы помогут!
Использование компонента OpenCV: ОС Windows 7/8/8/10 (Windows Server только с 2016)
В июле 2022 будет выпущен релиз pyOpenRPA, который будет работать на ОС семейства Linux (Ubuntu / Astra)
- ОС Windows 7+ / Server 2008+.
- RAM 512+ Гб
- ОС Windows 7+ / Server 2008+ / Linux
- RAM 512+ Мб
- HDD 2+ Гб
- CPU 1+ ГГц

@ -1,4 +1,4 @@
cd %~dp0
cd /d %~dp0
@ECHO OFF
RD /S /Q "%~dp0..\..\Wiki\RUS_Guide\doctrees"
RD /S /Q "%~dp0..\..\Wiki\RUS_Guide\html"

@ -1,4 +1,4 @@
cd %~dp0
cd /d %~dp0
RD /S /Q "%~dp0..\Wiki\RUS_Guide\doctrees"
RD /S /Q "%~dp0..\Wiki\RUS_Guide\html"
RD /S /Q "%~dp0..\Wiki\RUS_Guide\markdown"

@ -42,20 +42,21 @@ def O2A_Loop(inGSettings):
lBodyLenInt = len(lRequestBody)
if lBodyLenInt != 0: # CHeck if not empty result when close the connection from orch
lQueueList = lResponse.json() # Try to get JSON
for lQueueItem in lQueueList:
# Append QUEUE item in ProcessorDict > ActivityList
lActivityLastGUIDStr = lQueueItem["GUIDStr"]
# Check if ActivityItem ["ThreadBool"] = False > go sync mode in processor queue; Else: New thread
if lQueueItem.get("ThreadBool",False) == False:
inGSettings["ProcessorDict"]["ActivityList"].append(lQueueItem)
else:
Processor.ProcessorRunAsync(inGSettings=inGSettings,inActivityList=[lQueueItem])
# Log full version if bytes size is less than limit . else short
lAgentLimitLogSizeBytesInt = 500
if lBodyLenInt <= lAgentLimitLogSizeBytesInt:
if lL: lL.info(f"Словарь ActivityItem, полученный от оркестратора: {lQueueItem}")
else:
if lL: lL.info(f"Словарь ActivityItem, полученный от оркестратора: содержимое свернуто из-за большого размера ({lBodyLenInt} байт")
if lQueueList != None:
for lQueueItem in lQueueList:
# Append QUEUE item in ProcessorDict > ActivityList
lActivityLastGUIDStr = lQueueItem["GUIDStr"]
# Check if ActivityItem ["ThreadBool"] = False > go sync mode in processor queue; Else: New thread
if lQueueItem.get("ThreadBool",False) == False:
inGSettings["ProcessorDict"]["ActivityList"].append(lQueueItem)
else:
Processor.ProcessorRunAsync(inGSettings=inGSettings,inActivityList=[lQueueItem])
# Log full version if bytes size is less than limit . else short
lAgentLimitLogSizeBytesInt = 500
if lBodyLenInt <= lAgentLimitLogSizeBytesInt:
if lL: lL.info(f"Словарь ActivityItem, полученный от оркестратора: {lQueueItem}")
else:
if lL: lL.info(f"Словарь ActivityItem, полученный от оркестратора: содержимое свернуто из-за большого размера ({lBodyLenInt} байт")
else:
if lL: lL.debug(f"Поступил пустой ответ от оркестратора - цикл попытается обновить соединение между оркестратором и агентом");
except requests.exceptions.ConnectionError as e:

@ -158,20 +158,19 @@ class ControlPanel():
lHTMLStr = self.mInitJSJinja2Template.render(**inDataDict) # Render the template into str
return lHTMLStr
def DataDictGenerate(self, inRequest):
"""
def DataDictGenerate(self, inAuthTokenStr=None):
""" Сформировать словарь для генерации шаблона
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
:param inAuthTokenStr: Текстовый токен авторизации пользователя
:return:
"""
lData = {
"StorageRobotDict": None,
"ControlPanelInstance":self,
"OrchestratorModule":Orchestrator,
"RequestInstance": inRequest,
"UserInfoDict": Orchestrator.WebUserInfoGet(inRequest=inRequest),
"UserUACDict": Orchestrator.UACUserDictGet(inRequest=inRequest),
"UserUACCheckDef": inRequest.UACClientCheck,
"UserInfoDict": Orchestrator.WebUserInfoGet(inAuthTokenStr=inAuthTokenStr),
"UserUACDict": Orchestrator.WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr),
"UserUACCheckDef": lambda inKeyList: Orchestrator.WebUserUACCheck(inAuthTokenStr=inAuthTokenStr,inKeyList=inKeyList),
"EnumerateDef": enumerate,
"OperatorModule": operator,
"MathModule": math
@ -184,36 +183,36 @@ class ControlPanel():
lData.update(self.mJinja2DataUpdateDict)
return lData
def OnRefreshHTMLStr(self, inRequest):
def OnRefreshHTMLStr(self, inAuthTokenStr=None):
"""
Event to generate HTML code of the control panel when refresh time is over.
Support backward compatibility for previous versions.
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
:param inAuthTokenStr: request handler (from http.server import BaseHTTPRequestHandler)
:return:
"""
lHTMLStr = None
lL = Orchestrator.OrchestratorLoggerGet()
if self.mBackwardCompatibilityHTMLDef is None:
if self.mRefreshHTMLJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mRefreshHTMLJinja2TemplateFileNameStr is not None):
lDataDict = self.OnRefreshHTMLDataDict(inRequest = inRequest)
lDataDict = self.OnRefreshHTMLDataDict(inAuthTokenStr = inAuthTokenStr)
# Jinja code
lHTMLStr = self.RefreshHTMLJinja2StrGenerate(inDataDict=lDataDict)
else:
lHTMLStr = self.BackwardAdapterHTMLDef(inRequest=inRequest)
lHTMLStr = self.BackwardAdapterHTMLDef(inAuthTokenStr=inAuthTokenStr)
# return the str
return lHTMLStr
def OnRefreshHTMLDataDict(self, inRequest):
def OnRefreshHTMLDataDict(self, inAuthTokenStr):
"""
Event to prepare data context for the futher Jinja2 HTML generation. You can override this def if you want some thing more data
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
:return: dict
"""
return self.DataDictGenerate(inRequest=inRequest)
return self.DataDictGenerate(inAuthTokenStr=inAuthTokenStr)
def OnRefreshHTMLHashStr(self, inRequest):
def OnRefreshHTMLHashStr(self, inAuthTokenStr):
"""
Generate the hash the result output HTML. You can override this function if you know how to optimize HTML rendering.
TODO NEED TO MODIFY ServerSettings to work with Hash because of all defs are need do use Hash
@ -223,7 +222,7 @@ class ControlPanel():
"""
return None
def OnRefreshJSONDict(self, inRequest):
def OnRefreshJSONDict(self, inAuthTokenStr):
"""
Event to transmit some data from server side to the client side in JSON format. Call when page refresh is initialized
@ -234,36 +233,36 @@ class ControlPanel():
if self.mBackwardCompatibilityJSONDef is None:
pass
else:
lResultDict = self.BackwardAdapterJSONDef(inRequest=inRequest)
lResultDict = self.BackwardAdapterJSONDef(inAuthTokenStr=inAuthTokenStr)
return lResultDict
def OnInitJSStr(self, inRequest):
def OnInitJSStr(self, inAuthTokenStr):
"""
Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once.
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
:param inAuthTokenStr: request handler (from http.server import BaseHTTPRequestHandler)
:return: ""
"""
lJSStr = ""
if self.mBackwardCompatibilityJSDef is None:
if self.mInitJSJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mInitJSJinja2TemplateFileNameStr is not None):
lDataDict = self.OnInitJSDataDict(inRequest = inRequest)
lDataDict = self.OnInitJSDataDict(inAuthTokenStr = inAuthTokenStr)
# Jinja code
lJSStr = self.InitJSJinja2StrGenerate(inDataDict=lDataDict)
else:
lJSStr = self.BackwardAdapterJSDef(inRequest=inRequest)
lJSStr = self.BackwardAdapterJSDef(inAuthTokenStr=inAuthTokenStr)
return lJSStr
def OnInitJSDataDict(self, inRequest):
def OnInitJSDataDict(self, inAuthTokenStr):
"""
Event to prepare data context for the futher Jinja2 JS init generation. You can override this def if you want some thing more data
:param inRequest: request handler (from http.server import BaseHTTPRequestHandler)
:return: dict
"""
return self.DataDictGenerate(inRequest=inRequest)
return self.DataDictGenerate(inAuthTokenStr=inAuthTokenStr)
def BackwardAdapterHTMLDef(self,inRequest):
def BackwardAdapterHTMLDef(self,inAuthTokenStr):
lGS = Orchestrator.GSettingsGet()
lL = Orchestrator.OrchestratorLoggerGet()
# HTMLRenderDef
@ -277,7 +276,7 @@ class ControlPanel():
if lDEFARGLen == 1: # def (inGSettings)
lHTMLResult = lItemHTMLRenderDef(lGS)
elif lDEFARGLen == 2: # def (inRequest, inGSettings)
lHTMLResult = lItemHTMLRenderDef(inRequest, lGS)
lHTMLResult = lItemHTMLRenderDef(inAuthTokenStr, lGS)
elif lDEFARGLen == 0: # def ()
lHTMLResult = lItemHTMLRenderDef()
# RunFunction
@ -294,7 +293,7 @@ class ControlPanel():
return lResultStr
def BackwardAdapterJSONDef(self,inRequest):
def BackwardAdapterJSONDef(self,inAuthTokenStr):
lGS = Orchestrator.GSettingsGet()
lL = Orchestrator.OrchestratorLoggerGet()
# HTMLRenderDef
@ -308,7 +307,7 @@ class ControlPanel():
if lDEFARGLen == 1: # def (inGSettings)
lJSONResult = lItemJSONGeneratorDef(lGS)
elif lDEFARGLen == 2: # def (inRequest, inGSettings)
lJSONResult = lItemJSONGeneratorDef(inRequest, lGS)
lJSONResult = lItemJSONGeneratorDef(inAuthTokenStr, lGS)
elif lDEFARGLen == 0: # def ()
lJSONResult = lItemJSONGeneratorDef()
# RunFunction
@ -323,7 +322,7 @@ class ControlPanel():
f"Ошибка при формирвоании JSON (JSONGeneratorDef). Идентификатор панели управления {self.mControlPanelNameStr}")
return lResultDict
def BackwardAdapterJSDef(self,inRequest):
def BackwardAdapterJSDef(self,inAuthTokenStr):
lGS = Orchestrator.GSettingsGet()
lL = Orchestrator.OrchestratorLoggerGet()
# HTMLRenderDef
@ -337,7 +336,7 @@ class ControlPanel():
if lDEFARGLen == 1: # def (inGSettings)
lJSResult = lJSInitGeneratorDef(lGS)
elif lDEFARGLen == 2: # def (inRequest, inGSettings)
lJSResult = lJSInitGeneratorDef(inRequest, lGS)
lJSResult = lJSInitGeneratorDef(inAuthTokenStr, lGS)
elif lDEFARGLen == 0: # def ()
lJSResult = lJSInitGeneratorDef()
if type(lJSResult) is str:

@ -87,21 +87,23 @@ def RobotRDPActive(inGSettings, inThreadControlDict):
lRDPSessionKeyStr = inGlobalDict["FullScreenRDPSessionKeyStr"] # Get the RDPSessionKeyStr
if lRDPSessionKeyStr in inGlobalDict["RDPList"]: # Session Key is in dict
lRDPConfigurationDict = inGlobalDict["RDPList"][lRDPSessionKeyStr]
#if not lRDPConfigurationDict["SessionIsIgnoredBool"]: # Session is not ignored
# Check if full screen
lIsFullScreenBool = Connector.SessionIsFullScreen(inSessionHexStr=lRDPConfigurationDict["SessionHex"])
if not lIsFullScreenBool: # If not the full screen
# Check all RDP window and minimize it
for lRDPSessionKeyStrItem in inGlobalDict["RDPList"]:
lRDPConfigurationDictItem = inGlobalDict["RDPList"][lRDPSessionKeyStrItem]
if Connector.SessionIsFullScreen(inSessionHexStr=lRDPConfigurationDictItem["SessionHex"]):
if inThreadControlDict["ThreadExecuteBool"] == True: # TEST FEATURE BEFORE ONE THREAD INTEGRATION
Connector.SessionScreenSize_X_Y_W_H(inSessionHex=lRDPConfigurationDictItem["SessionHex"], inXInt=10, inYInt=10,
inWInt=550,
inHInt=350) # Prepare little window
# Set full screen for new window
if inThreadControlDict["ThreadExecuteBool"] == True: # TEST FEATURE BEFORE ONE THREAD INTEGRATION
Connector.SessionScreenFull(inSessionHex=lRDPConfigurationDict["SessionHex"], inLogger= inGSettings["Logger"], inRDPConfigurationItem=inGlobalDict["RDPList"][lRDPSessionKeyStrItem])
# SET FULL SCREEN ONLY IF NOT IGNORED
if not lRDPConfigurationDict["SessionIsIgnoredBool"]:
#if not lRDPConfigurationDict["SessionIsIgnoredBool"]: # Session is not ignored
# Check if full screen
lIsFullScreenBool = Connector.SessionIsFullScreen(inSessionHexStr=lRDPConfigurationDict["SessionHex"])
if not lIsFullScreenBool: # If not the full screen
# Check all RDP window and minimize it
for lRDPSessionKeyStrItem in inGlobalDict["RDPList"]:
lRDPConfigurationDictItem = inGlobalDict["RDPList"][lRDPSessionKeyStrItem]
if Connector.SessionIsFullScreen(inSessionHexStr=lRDPConfigurationDictItem["SessionHex"]):
if inThreadControlDict["ThreadExecuteBool"] == True: # TEST FEATURE BEFORE ONE THREAD INTEGRATION
Connector.SessionScreenSize_X_Y_W_H(inSessionHex=lRDPConfigurationDictItem["SessionHex"], inXInt=10, inYInt=10,
inWInt=550,
inHInt=350) # Prepare little window
# Set full screen for new window
if inThreadControlDict["ThreadExecuteBool"] == True: # TEST FEATURE BEFORE ONE THREAD INTEGRATION
Connector.SessionScreenFull(inSessionHex=lRDPConfigurationDict["SessionHex"], inLogger= inGSettings["Logger"], inRDPConfigurationItem=inGlobalDict["RDPList"][lRDPSessionKeyStrItem])
else:
# Check all RDP window and minimize it
for lRDPSessionKeyStrItem in inGlobalDict["RDPList"]:

@ -8,6 +8,7 @@
#from http.client import HTTPException
import threading
import typing
from pyOpenRPA.Tools import CrossOS
@ -17,9 +18,11 @@ from . import ServerBC
# объявление import
from fastapi import FastAPI, Form, Request, HTTPException, Depends, Header, Response, Body
from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse
from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse, RedirectResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.encoders import jsonable_encoder
from starlette.datastructures import MutableHeaders
from pydantic import BaseModel
import uvicorn
import io
@ -27,6 +30,7 @@ from starlette.responses import StreamingResponse
from typing import Union
from pyOpenRPA import __version__
import requests
import base64
import uuid
import datetime
@ -42,6 +46,8 @@ app = FastAPI(
swagger_ui_oauth2_redirect_url = "/orpa/fastapi/docs/oauth2-redirect",
)
def IdentifyAuthorize(inRequest:Request, inResponse:Response,
inCookiesStr: Union[str, None] = Header(default=None,alias="Cookie"),
inAuthorizationStr: Union[str, None] = Header(default="",alias="Authorization")):
@ -69,47 +75,62 @@ def IdentifyAuthorize(inRequest:Request, inResponse:Response,
return lCookieAuthToken
######################################
#Way 2 - try to logon
if len(lHeaderAuthorization) == 2:
llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[1]).decode("utf-8").split(
":")
lUser = llHeaderAuthorizationDecodedUserPasswordList[0]
lPassword = llHeaderAuthorizationDecodedUserPasswordList[1]
lDomain = ""
if "\\" in lUser:
lDomain = lUser.split("\\")[0]
lUser = lUser.split("\\")[1]
lLogonBool = __Orchestrator__.OSCredentialsVerify(inUserStr=lUser, inPasswordStr=lPassword, inDomainStr=lDomain)
#Check result
if lLogonBool:
lResult["Domain"] = lDomain
lResult["User"] = lUser
#Create token
lAuthToken=str(uuid.uuid1())
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken] = {}
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["Domain"] = lResult["Domain"]
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["User"] = lResult["User"]
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["FlagDoNotExpire"] = False
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["TokenDatetime"] = datetime.datetime.now()
#Set-cookie
inResponse.set_cookie(key="AuthToken",value=lAuthToken)
mOpenRPA={}
mOpenRPA["AuthToken"] = lAuthToken
mOpenRPA["Domain"] = lResult["Domain"]
mOpenRPA["User"] = lResult["User"]
mOpenRPA["IsSuperToken"] = __Orchestrator__.GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(mOpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False)
return lAuthToken
#inRequest.OpenRPASetCookie = {}
#New engine of server
#inRequest.OpenRPAResponseDict["SetCookies"]["AuthToken"] = lAuthToken
if lHeaderAuthorization != ['']:
if "AuthExc" in lCookies:
raise AuthException()
else:
raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неверная пара логин / пароль)", headers={})
######################################
llHeaderAuthorizationDecodedUserPasswordList = base64.b64decode(lHeaderAuthorization[0]).decode("utf-8").split(":")
lUser = llHeaderAuthorizationDecodedUserPasswordList[0]
lPassword = llHeaderAuthorizationDecodedUserPasswordList[1]
lDomain = ""
if "\\" in lUser:
lDomain = lUser.split("\\")[0]
lUser = lUser.split("\\")[1]
elif "@" in lUser:
lDomain = lUser.split("@")[1]
lUser = lUser.split("@")[0]
lLogonBool = __Orchestrator__.OSCredentialsVerify(inUserStr=lUser, inPasswordStr=lPassword, inDomainStr=lDomain)
#Check result
if lLogonBool: # check user in gsettings rules
lLogonBool = False
gSettings = __Orchestrator__.GSettingsGet() # Set the global settings
lUserTurple = (lDomain.upper(),lUser.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"]
lUserTurple2 = ("",lUser.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"]
if lUserTurple in gSettings.get("ServerDict",{}).get("AccessUsers", {}).get("RuleDomainUserDict", {}): lLogonBool = True
elif lUserTurple2 in gSettings.get("ServerDict",{}).get("AccessUsers", {}).get("RuleDomainUserDict", {}): lLogonBool = True
if lLogonBool: # If user exists in UAC Dict
lResult["Domain"] = lDomain
lResult["User"] = lUser
#Create token
lAuthToken=str(uuid.uuid1())
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken] = {}
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["Domain"] = lResult["Domain"]
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["User"] = lResult["User"]
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["FlagDoNotExpire"] = False
__Orchestrator__.GSettingsGet()["ServerDict"]["AccessUsers"]["AuthTokensDict"][lAuthToken]["TokenDatetime"] = datetime.datetime.now()
#Set-cookie
inResponse.set_cookie(key="AuthToken",value=lAuthToken)
mOpenRPA={}
mOpenRPA["AuthToken"] = lAuthToken
mOpenRPA["Domain"] = lResult["Domain"]
mOpenRPA["User"] = lResult["User"]
mOpenRPA["IsSuperToken"] = __Orchestrator__.GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(mOpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False)
raise ReloadPage(token=lAuthToken)
#inRequest.OpenRPASetCookie = {}
#New engine of server
#inRequest.OpenRPAResponseDict["SetCookies"]["AuthToken"] = lAuthToken
else:
errorMsg = "Попытка авторизации не прошла успешно (для пользователя не заявлен доступ к оркестратору pyOpenRPA. Обратитесь в техническую поддержку)"
raise ErrorException(text=errorMsg)
else:
errorMsg = "Попытка авторизации не прошла успешно (неверная пара логин / пароль)"
raise ErrorException(text=errorMsg)
else:
raise HTTPException(status_code=401, detail="Попытка авторизации не прошла успешно (неполная пара логин / пароль)", headers={'Content-type':'text/html', 'WWW-Authenticate':'Basic'})
raise AuthException()
else: return None # Credentials are not required - return none
# Перевод встроенных fastapi функций на авторизацию
lRouteList =[]
for lItem in app.router.routes:
lRouteList.append(lItem)
@ -123,32 +144,81 @@ for lItem in lRouteList:
tags=["FastAPI"]
)
# объявление классов для дальнейшей обработки вызываемых исключений (обязательно должны наследоваться от EXception)
class ErrorException(Exception):
def __init__(self, text :str, name: str="AuthExc"):
self.name = name
self.text = text
class AuthException(Exception):
def __init__(self, name: str="AuthTryWindowCreate"):
self.name = name
class ReloadPage(Exception):
def __init__(self, token :str, name: str="AuthToken"):
self.name = name
self.token = token
templates = Jinja2Templates(directory=CrossOS.PathJoinList(CrossOS.PathSplitList(__file__)[:-2] + ["Resources","Web","orpa"]))
# Обработчик ошибки авторизации (вывод информации о причинах неудачной авторизации)
@app.exception_handler(ErrorException)
async def unicorn_exception_handler(request: Request, exc:ErrorException):
response = templates.TemplateResponse(status_code=401,name="badAuth.xhtml", context={"request":request, "errorMsg":exc.text, "title":"ОРКЕСТРАТОР PYOPENRPA", "subtitle":"АВТОРИЗАЦИЯ", "version":__version__})
response.set_cookie(key="AuthExc",value="True")
return response
# Обработчик успешной попытки авторизации (обновление страницы + установки куки-токена)
@app.exception_handler(ReloadPage)
async def unicorn_exception_handler_3(request: Request, exc:ReloadPage):
response = HTMLResponse(content="", status_code=200)
response.set_cookie(key=exc.name, value=exc.token)
try:response.delete_cookie(key="AuthExc")
except Exception:pass
return response
# Обработчик попытки авторизации (отвечает за рендер формы для ввода пары логин / пароль)
@app.exception_handler(AuthException)
def unicorn_exception_handler_2(request: Request, exc: AuthException):
response = templates.TemplateResponse(status_code=401,name="auth.xhtml", context={"request":request, "title":"ОРКЕСТРАТОР PYOPENRPA", "subtitle":"АВТОРИЗАЦИЯ", "version":__version__})
try:response.delete_cookie(key="AuthExc")
except Exception:pass
return response
from . import ServerSettings
def BackwardCompatibility(inRequest:Request, inResponse:Response, inBodyStr:str = Body(""), inAuthTokenStr = None):
async def BackwardCompatibility(inRequest:Request, inResponse:Response, inAuthTokenStr = None):
lHTTPRequest = ServerBC.HTTPRequestOld(inRequest=inRequest, inResponse=inResponse, inAuthTokenStr=inAuthTokenStr)
lHTTPRequest.path = inRequest.url.path
#print(f"WEB START: {lHTTPRequest.path}")
inBodyStr = await inRequest.body()
if inBodyStr == None: inBodyStr = ""
else: inBodyStr = inBodyStr.decode("utf8")
lHTTPRequest.body = inBodyStr
lHTTPRequest.client_address = [inRequest.client.host]
threading.current_thread().request = lHTTPRequest
lResult = lHTTPRequest.do_GET(inBodyStr=inBodyStr)
if lResult is None:
if inRequest.method=="GET":
lResult = lHTTPRequest.do_GET(inBodyStr=inBodyStr)
elif inRequest.method=="POST":
lResult = lHTTPRequest.do_POST(inBodyStr=inBodyStr)
#print(f"WEB STOP: {lHTTPRequest.path}")
if lHTTPRequest.OpenRPAResponseDict["Headers"]["Content-type"] != None:
return StreamingResponse(io.BytesIO(lResult), media_type=lHTTPRequest.OpenRPAResponseDict["Headers"]["Content-type"])
#WRAPPERS!
def BackwardCompatibityWrapperAuth(inRequest:Request, inResponse:Response, inBodyStr:str = Body(""),
async def BackwardCompatibityWrapperAuth(inRequest:Request, inResponse:Response,
inAuthTokenStr:str=Depends(ServerSettings.IdentifyAuthorize)): # Old from v1.3.1 (updated to FastAPI)
return BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inBodyStr = inBodyStr, inAuthTokenStr=inAuthTokenStr)
def BackwardCompatibityWrapperNoAuth(inRequest:Request, inResponse:Response, inBodyStr:str = Body("")): # Old from v1.3.1 (updated to FastAPI)
return BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inBodyStr = inBodyStr, inAuthTokenStr=None)
def BackwardCompatibityBeginWrapperAuth(inBeginTokenStr, inRequest:Request, inResponse:Response, inBodyStr:str = Body(""),
return await BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inAuthTokenStr=inAuthTokenStr)
async def BackwardCompatibityWrapperNoAuth(inRequest:Request, inResponse:Response): # Old from v1.3.1 (updated to FastAPI)
return await BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inAuthTokenStr=None)
async def BackwardCompatibityBeginWrapperAuth(inBeginTokenStr, inRequest:Request, inResponse:Response,
inAuthTokenStr:str=Depends(ServerSettings.IdentifyAuthorize)): # Old from v1.3.1 (updated to FastAPI)
return BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inBodyStr = inBodyStr, inAuthTokenStr=inAuthTokenStr)
def BackwardCompatibityBeginWrapperNoAuth(inBeginTokenStr, inRequest:Request, inResponse:Response, inBodyStr:str = Body("")): # Old from v1.3.1 (updated to FastAPI)
return BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inBodyStr = inBodyStr, inAuthTokenStr=None)
return await BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inAuthTokenStr=inAuthTokenStr)
async def BackwardCompatibityBeginWrapperNoAuth(inBeginTokenStr, inRequest:Request, inResponse:Response): # Old from v1.3.1 (updated to FastAPI)
return await BackwardCompatibility(inRequest = inRequest, inResponse = inResponse, inAuthTokenStr=None)
@ -165,15 +235,19 @@ def InitFastAPI():
ServerSettings.SettingsUpdate()
BCURLUpdate()
def BCURLUpdate():
def BCURLUpdate(inExceptionFlagBool:bool=True):
for lConnectItemDict in __Orchestrator__.GSettingsGet()["ServerDict"]["URLList"]:
if "BCBool" not in lConnectItemDict:
if "ResponseFolderPath" in lConnectItemDict:
app.mount(lConnectItemDict["URL"],
StaticFiles(directory=CrossOS.PathStr(lConnectItemDict["ResponseFolderPath"])),
name=lConnectItemDict["URL"].replace('/',"_"))
try:
app.mount(lConnectItemDict["URL"],
StaticFiles(directory=CrossOS.PathStr(lConnectItemDict["ResponseFolderPath"])),
name=lConnectItemDict["URL"].replace('/',"_"))
except:
if inExceptionFlagBool: raise RuntimeError("Fatal error. Bad FolderPath")
else: pass
else:
if lConnectItemDict.get("MatchType") in ["EqualCase", "Equal","EqualNoParam"]:
if lConnectItemDict.get("MatchType") in ["BeginWith", "EqualCase", "Equal","EqualNoParam"]:
if lConnectItemDict.get("UACBool",True):
app.add_api_route(
path=lConnectItemDict["URL"],

@ -30,31 +30,34 @@ import io
from starlette.responses import StreamingResponse
from typing import Union
from fastapi.responses import JSONResponse
import asyncio
# # # # # # # # # # # #
# v 1.2.0 Functionallity
# # # # # # # # # # # #
# Generate JS when page init
def HiddenJSInitGenerate(inRequest, inGSettings):
dUAC = inRequest.UACClientCheck # Alias.
def HiddenJSInitGenerate(inAuthTokenStr):
inGSettings = __Orchestrator__.GSettingsGet()
dUAC = lambda inKeyList: __Orchestrator__.WebUserUACCheck(inAuthTokenStr=inAuthTokenStr, inKeyList=inKeyList)
lUACCPTemplateKeyList=["pyOpenRPADict","CPKeyDict"]
lL = inGSettings["Logger"] # Alias for logger
lJSInitResultStr = ""
lRenderFunctionsRobotDict = inGSettings["ServerDict"]["ControlPanelDict"]
for lItemKeyStr in lRenderFunctionsRobotDict:
lItemDict = lRenderFunctionsRobotDict[lItemKeyStr]
lUACBool = dUAC(inRoleKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC)
lUACBool = dUAC(inKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC)
if lItemKeyStr=="VersionCheck": lUACBool=True # For backward compatibility for the old fron version which not reload page when new orch version is comming
if lUACBool: # Run function if UAC is TRUE
# JSONGeneratorDef
lJSInitResultStr = lJSInitResultStr + ";" + lItemDict.OnInitJSStr(inRequest=inRequest)
lJSInitResultStr = lJSInitResultStr + ";" + lItemDict.OnInitJSStr(inAuthTokenStr=inAuthTokenStr)
return lJSInitResultStr
# Generate CP HTML + JSON
# Return {"Key":{"",""}}
def HiddenCPDictGenerate(inRequest, inGSettings):
dUAC = inRequest.UACClientCheck # Alias.
def HiddenCPDictGenerate(inAuthTokenStr):
inGSettings = __Orchestrator__.GSettingsGet()
dUAC = lambda inKeyList: __Orchestrator__.WebUserUACCheck(inAuthTokenStr=inAuthTokenStr, inKeyList=inKeyList)
#dUAC = d_uac #inRequest.UACClientCheck # Alias.
lUACCPTemplateKeyList=["pyOpenRPADict","CPKeyDict"]
lL = inGSettings["Logger"] # Alias for logger
# Create result JSON
@ -62,15 +65,15 @@ def HiddenCPDictGenerate(inRequest, inGSettings):
lRenderFunctionsRobotDict = inGSettings["ServerDict"]["ControlPanelDict"]
for lItemKeyStr in lRenderFunctionsRobotDict:
lItemDict = lRenderFunctionsRobotDict[lItemKeyStr]
lUACBool = dUAC(inRoleKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC)
lUACBool = dUAC(inKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC)
if lItemKeyStr=="VersionCheck": lUACBool=True # For backward compatibility for the old fron version which not reload page when new orch version is comming
if lUACBool: # Run function if UAC is TRUE
lCPItemDict = {"HTMLStr": None, "JSONDict":None}
try:
# HTML Render
lCPItemDict["HTMLStr"] = lItemDict.OnRefreshHTMLStr(inRequest=inRequest)
lCPItemDict["HTMLStr"] = lItemDict.OnRefreshHTMLStr(inAuthTokenStr=inAuthTokenStr)
# JSONGeneratorDef
lCPItemDict["JSONDict"] = lItemDict.OnRefreshJSONDict(inRequest=inRequest)
lCPItemDict["JSONDict"] = lItemDict.OnRefreshJSONDict(inAuthTokenStr=inAuthTokenStr)
except Exception as e:
lL.exception(f"EXCEPTION WHEN HTML/ JSON RENDER")
# Insert CPItemDict in result CPDict
@ -78,14 +81,15 @@ def HiddenCPDictGenerate(inRequest, inGSettings):
return lCPDict
# Return {"Key":{"",""}}
def HiddenRDPDictGenerate(inRequest, inGSettings):
dUAC = inRequest.UACClientCheck # Alias.
def HiddenRDPDictGenerate(inAuthTokenStr):
inGSettings = __Orchestrator__.GSettingsGet()
dUAC = lambda inKeyList: __Orchestrator__.WebUserUACCheck(inAuthTokenStr=inAuthTokenStr, inKeyList=inKeyList)
lUACRDPTemplateKeyList=["pyOpenRPADict","RDPKeyDict"]
lRDPDict = {"HandlebarsList":[]}
# Iterate throught the RDP list
for lRDPSessionKeyStrItem in inGSettings["RobotRDPActive"]["RDPList"]:
# Check UAC
if dUAC(inRoleKeyList=lUACRDPTemplateKeyList+[lRDPSessionKeyStrItem]):
if dUAC(inKeyList=lUACRDPTemplateKeyList+[lRDPSessionKeyStrItem]):
lRDPConfiguration = inGSettings["RobotRDPActive"]["RDPList"][
lRDPSessionKeyStrItem] # Get the configuration dict
lDataItemDict = {"SessionKeyStr": "", "SessionHexStr": "", "IsFullScreenBool": False,
@ -102,15 +106,16 @@ def HiddenRDPDictGenerate(inRequest, inGSettings):
return lRDPDict
# Return {"HostNameUpperStr;UserUpperStr":{"IsListenBool":True}, "HandlebarsList":[{"HostnameUpperStr":"","UserUpperStr":"","IsListenBool":True}]}
def HiddenAgentDictGenerate(inRequest, inGSettings):
dUAC = inRequest.UACClientCheck # Alias.
def HiddenAgentDictGenerate(inAuthTokenStr):
inGSettings = __Orchestrator__.GSettingsGet()
dUAC = lambda inKeyList: __Orchestrator__.WebUserUACCheck(inAuthTokenStr=inAuthTokenStr, inKeyList=inKeyList)
lUACAgentTemplateKeyList=["pyOpenRPADict","AgentKeyDict"]
lAgentDict = {"HandlebarsList":[]}
# Iterate throught the RDP list
for lAgentItemKeyStrItem in inGSettings["AgentDict"]:
# Check UAC
lKeyStr = f"{lAgentItemKeyStrItem[0]};{lAgentItemKeyStrItem[1]}" # turple ("HostNameUpperStr","UserUpperStr") > Str "HostNameUpperStr;UserUpperStr"
if dUAC(inRoleKeyList=lUACAgentTemplateKeyList+[lKeyStr]):
if dUAC(inKeyList=lUACAgentTemplateKeyList+[lKeyStr]):
lDataItemDict = inGSettings["AgentDict"][lAgentItemKeyStrItem]
lDataItemAgentDict = copy.deepcopy(lDataItemDict)
lDataItemAgentDict["ActivityList"] = []
@ -128,17 +133,22 @@ def HiddenAgentDictGenerate(inRequest, inGSettings):
# Client: mGlobal.pyOpenRPA.ServerDataHashStr
# Client: mGlobal.pyOpenRPA.ServerDataDict
def pyOpenRPA_ServerData(inRequest,inGSettings):
@app.post("/orpa/client/server-data",response_class=JSONResponse, tags=["Client"])
#{"Method": "POST", "URL": "/orpa/client/server-data", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerData, "ResponseContentType": "application/json"},
async def pyOpenRPA_ServerData(inRequest: Request, inAuthTokenStr:str=Depends(IdentifyAuthorize)):
inGSettings = __Orchestrator__.GSettingsGet()
# Extract the hash value from request
lValueStr = inRequest.body
lValueStr = await inRequest.body()
lValueStr= lValueStr.decode("utf8")
# Generate ServerDataDict
lFlagDoGenerateBool = True
while lFlagDoGenerateBool:
lServerDataDict = {
"CPDict": HiddenCPDictGenerate(inRequest=inRequest, inGSettings=inGSettings),
"RDPDict": HiddenRDPDictGenerate(inRequest=inRequest, inGSettings=inGSettings),
"AgentDict": HiddenAgentDictGenerate(inRequest=inRequest, inGSettings=inGSettings),
"UserDict": {"UACClientDict": inRequest.OpenRPA["DefUserRoleHierarchyGet"](), "CWDPathStr": os.getcwd(), "VersionStr": inGSettings["VersionStr"]},
"CPDict": HiddenCPDictGenerate(inAuthTokenStr=inAuthTokenStr),
"RDPDict": HiddenRDPDictGenerate(inAuthTokenStr=inAuthTokenStr),
"AgentDict": HiddenAgentDictGenerate(inAuthTokenStr=inAuthTokenStr),
"UserDict": {"UACClientDict": __Orchestrator__.WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr), "CWDPathStr": os.getcwd(), "VersionStr": inGSettings["VersionStr"]},
}
# Create JSON
lServerDataDictJSONStr = json.dumps(lServerDataDict)
@ -147,33 +157,31 @@ def pyOpenRPA_ServerData(inRequest,inGSettings):
if lValueStr!=lServerDataHashStr and lServerDataHashStr!= "" and lServerDataHashStr!= None: # Case if Hash is not equal
lFlagDoGenerateBool = False
else: # Case Hashes are equal
time.sleep(inGSettings["Client"]["Session"]["ControlPanelRefreshIntervalSecFloat"])
await asyncio.sleep(inGSettings["Client"]["Session"]["ControlPanelRefreshIntervalSecFloat"])
# Return the result if Hash is changed
lResult = {"HashStr": lServerDataHashStr, "ServerDataDict": lServerDataDict}
inResponseDict = inRequest.OpenRPAResponseDict
# Send message back to client
message = json.dumps(lResult)
# Write content as utf-8 data
inResponseDict["Body"] = bytes(message, "utf8")
return lResult
# GET
# /pyOpenRPA/ServerJSInit return JavaScript to init on page
def pyOpenRPA_ServerJSInit(inRequest,inGSettings):
lResultStr = HiddenJSInitGenerate(inRequest=inRequest, inGSettings=inGSettings)
inResponseDict = inRequest.OpenRPAResponseDict
@app.get("/orpa/client/server-js-init",response_class=PlainTextResponse, tags=["Client"])
def pyOpenRPA_ServerJSInit(inRequest: Request, inAuthTokenStr:str=Depends(IdentifyAuthorize)):
lResultStr = HiddenJSInitGenerate(inAuthTokenStr=inAuthTokenStr)
if lResultStr is None:
lResultStr = ""
# Write content as utf-8 data
inResponseDict["Body"] = bytes(lResultStr, "utf8")
return lResultStr
#v1.2.0 Send data container to the client from the server
# /pyOpenRPA/ServerLog return {"HashStr" , "ServerLogList": ["row 1", "row 2"]}
# Client: mGlobal.pyOpenRPA.ServerLogListHashStr
# Client: mGlobal.pyOpenRPA.ServerLogList
def pyOpenRPA_ServerLog(inRequest,inGSDict):
@app.post("/orpa/client/server-log",response_class=JSONResponse, tags=["Client"])
async def pyOpenRPA_ServerLog(inRequest: Request, inAuthTokenStr:str=Depends(IdentifyAuthorize)):
inGSDict = __Orchestrator__.GSettingsGet()
# Extract the hash value from request
lValueStr = inRequest.body
lValueStr = await inRequest.body()
lValueStr= lValueStr.decode("utf8")
# Generate ServerDataDict
lFlagDoGenerateBool = True
while lFlagDoGenerateBool:
@ -183,14 +191,9 @@ def pyOpenRPA_ServerLog(inRequest,inGSDict):
if lValueStr!=lServerLogListHashStr and lServerLogListHashStr!= "" and lServerLogListHashStr!= None: # Case if Hash is not equal Fix because None can be obtained without JSON decode
lFlagDoGenerateBool = False
else: # Case Hashes are equal
time.sleep(inGSDict["Client"]["DumpLogListRefreshIntervalSecFloat"])
await asyncio.sleep(inGSDict["Client"]["DumpLogListRefreshIntervalSecFloat"])
# Return the result if Hash is changed
lResult = {"HashStr": lServerLogListHashStr, "ServerLogList": lServerLogList}
inResponseDict = inRequest.OpenRPAResponseDict
# Send message back to client
message = json.dumps(lResult)
# Write content as utf-8 data
inResponseDict["Body"] = bytes(message, "utf8")
return lResult
# Get thread list /orpa/threads
@ -247,7 +250,7 @@ def pyOpenRPA_Processor(inRequest:Request, inAuthTokenStr:str = Depends(Identify
lAsyncActivityList = []
for lActivityItem in lInput:
lResult.append(__Orchestrator__.ProcessorActivityItemAppend(inActivityItemDict=lActivityItem))
if lInput.get("ThreadBool", False) == False:
if lActivityItem.get("ThreadBool", False) == False:
lSyncActvityList.append(lActivityItem)
else:
lAsyncActivityList.append(lActivityItem)
@ -326,7 +329,7 @@ def pyOpenRPA_ActivityListExecute(inRequest:Request, inAuthTokenStr:str = Depend
# See docs in Agent (pyOpenRPA.Agent.O2A)
@app.post(path="/orpa/agent/o2a",response_class=JSONResponse,tags=["Agent"])
def pyOpenRPA_Agent_O2A(inRequest:Request, inAuthTokenStr:str = Depends(IdentifyAuthorize), inBodyDict = Body({})):
async def pyOpenRPA_Agent_O2A(inRequest:Request, inAuthTokenStr:str = Depends(IdentifyAuthorize), inBodyDict = Body({})):
inGSettings = __Orchestrator__.GSettingsGet()
lL = __Orchestrator__.OrchestratorLoggerGet()
lConnectionLifetimeSecFloat = inGSettings["ServerDict"]["AgentConnectionLifetimeSecFloat"] # 300.0 # 5 min * 60 sec 300.0
@ -394,9 +397,9 @@ def pyOpenRPA_Agent_O2A(inRequest:Request, inAuthTokenStr:str = Depends(Identify
lThisAgentDict["ConnectionCountInt"] -= 1 # Connection go to be closed - decrement the connection count
return lReturnActivityItemList
else: # Nothing to send - sleep for the next iteration
time.sleep(lAgentLoopSleepSecFloat)
await asyncio.sleep(lAgentLoopSleepSecFloat)
else: # no queue item - sleep for the next iteration
time.sleep(lAgentLoopSleepSecFloat)
await asyncio.sleep(lAgentLoopSleepSecFloat)
except Exception as e:
if lL: lL.exception("pyOpenRPA_Agent_O2A Exception!")
lThisAgentDict["ConnectionCountInt"] -= 1 # Connection go to be closed - decrement the connection count
@ -481,6 +484,7 @@ def SettingsUpdate():
gSettingsDict = __Orchestrator__.GSettingsGet()
if CrossOS.IS_WINDOWS_BOOL: lOrchestratorFolder = "\\".join(pyOpenRPA.Orchestrator.__file__.split("\\")[:-1])
if CrossOS.IS_LINUX_BOOL: lOrchestratorFolder = "/".join(pyOpenRPA.Orchestrator.__file__.split("/")[:-1])
lURLList = \
lURLList = \
[ #List of available URLs with the orchestrator server
#{
@ -494,9 +498,9 @@ def SettingsUpdate():
#}
#Orchestrator basic dependencies # Index page in server.py because of special settings
{"Method":"GET", "URL": gSettingsDict["ServerDict"]["URLIndexStr"],"MatchType": "EqualNoParam", "ResponseDefRequestGlobal": pyOpenRPA_Index},
{"Method":"GET", "URL": "/metadata.json", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\orpa\\metadata.json"), "ResponseContentType": "application/json"},
{"Method":"GET", "URL": "/metadata.json", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, CrossOS.PathStr("..\\Resources\\Web\\orpa\\metadata.json")), "ResponseContentType": "application/json","UACBool":False,},
#{"Method":"GET", "URL": "/Index.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "Web\\Index.js"), "ResponseContentType": "text/javascript"},
{"Method":"GET", "URL": "/orpa/resources/", "MatchType": "BeginWith", "ResponseFolderPath": os.path.join(lOrchestratorFolder, "..\\Resources"),"UACBool":False, "UseCacheBool": True},
{"Method":"GET", "URL": "/orpa/resources/", "MatchType": "BeginWith", "ResponseFolderPath": os.path.join(lOrchestratorFolder, CrossOS.PathStr("..\\Resources")),"UACBool":False, "UseCacheBool": True},
{"Method":"GET", "URL": "/orpa/client/resources/", "MatchType": "BeginWith", "ResponseFolderPath": os.path.join(lOrchestratorFolder, "Web"),"UACBool":False, "UseCacheBool": True},
#{"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", "UACBool":False, "UseCacheBool": True},
@ -505,16 +509,16 @@ def SettingsUpdate():
#{"Method":"GET", "URL": "/3rdParty/Google/LatoItalic.css", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Google\\LatoItalic.css"), "ResponseContentType": "font/css", "UACBool":False, "UseCacheBool": True},
#{"Method":"GET", "URL": "/3rdParty/Semantic-UI-CSS-master/themes/default/assets/fonts/icons.woff2", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Semantic-UI-CSS-master\\themes\\default\\assets\\fonts\\icons.woff2"), "ResponseContentType": "font/woff2", "UACBool":False, "UseCacheBool": True},
#{"Method":"GET", "URL": "/themes/default/", "MatchType": "BeginWith", "ResponseFolderPath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Semantic-UI-CSS-master\\themes\\default"),"UACBool":False, "UseCacheBool": True},
{"Method":"GET", "URL": "/favicon.ico", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\orpa\\favicon.ico"), "ResponseContentType": "image/x-icon", "UACBool":False, "UseCacheBool": True},
{"Method":"GET", "URL": "/favicon.ico", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, CrossOS.PathStr("..\\Resources\\Web\\orpa\\favicon.ico")), "ResponseContentType": "image/x-icon", "UACBool":False, "UseCacheBool": True},
#{"Method":"GET", "URL": "/3rdParty/Handlebars/handlebars-v4.1.2.js", "MatchType": "EqualCase", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\Handlebars\\handlebars-v4.1.2.js"), "ResponseContentType": "application/javascript", "UACBool":False, "UseCacheBool": True},
#{"Method": "GET", "URL": "/Monitor/ControlPanelDictGet", "MatchType": "Equal", "ResponseDefRequestGlobal": BackwardCompatibility.v1_2_0_Monitor_ControlPanelDictGet_SessionCheckInit, "ResponseContentType": "application/json"},
#{"Method": "GET", "URL": "/GetScreenshot", "MatchType": "BeginWith", "ResponseDefRequestGlobal": pyOpenRPA_Screenshot, "ResponseContentType": "image/png"},
#{"Method": "GET", "URL": "/pyOpenRPA_logo.png", "MatchType": "Equal", "ResponseFilePath": os.path.join(lOrchestratorFolder, "..\\Resources\\Web\\pyOpenRPA_logo.png"), "ResponseContentType": "image/png", "UACBool":False, "UseCacheBool": True},
{"Method": "POST", "URL": "/orpa/client/user-role-hierarchy-get", "MatchType": "Equal","ResponseDefRequestGlobal": BackwardCompatibility.v1_2_0_UserRoleHierarchyGet, "ResponseContentType": "application/json"},
# New way of the v.1.2.0 functionallity (all defs by the URL from /pyOpenRPA/...)
{"Method": "POST", "URL": "/orpa/client/server-data", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerData, "ResponseContentType": "application/json"},
{"Method": "GET", "URL": "/orpa/client/server-js-init", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerJSInit, "ResponseContentType": "application/javascript"},
{"Method": "POST", "URL": "/orpa/client/server-log", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerLog, "ResponseContentType": "application/json"},
#{"Method": "POST", "URL": "/orpa/client/server-data", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerData, "ResponseContentType": "application/json"},
#{"Method": "GET", "URL": "/orpa/client/server-js-init", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerJSInit, "ResponseContentType": "application/javascript"},
#{"Method": "POST", "URL": "/orpa/client/server-log", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ServerLog, "ResponseContentType": "application/json"},
#{"Method": "GET", "URL": "/orpa/client/screenshot-get", "MatchType": "Equal", "ResponseDefRequestGlobal": pyOpenRPA_Screenshot, "ResponseContentType": "image/png"},
# API
#{"Method": "POST", "URL": "/orpa/api/processor-queue-add", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_Processor, "ResponseContentType": "application/json"},

@ -331,8 +331,8 @@ def Create(inModeStr="BASIC", inLoggerLevel = None):
lResult = __Create__() # Create settings
# Создать файл логирования
# add filemode="w" to overwrite
if not os.path.exists("Reports"):
os.makedirs("Reports")
if not os.path.exists("Logs"):
os.makedirs("Logs")
##########################
# Подготовка логгера Robot
#########################
@ -342,7 +342,7 @@ def Create(inModeStr="BASIC", inLoggerLevel = None):
lL.setLevel(logging.INFO)
# create the logging file handler
mRobotLoggerFH = logging.FileHandler(
CrossOS.PathStr("Reports\\" + datetime.datetime.now().strftime("%Y_%m_%d") + ".log"))
CrossOS.PathStr("Logs\\" + datetime.datetime.now().strftime("%Y_%m_%d") + ".log"))
mRobotLoggerFormatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
mRobotLoggerFH.setFormatter(mRobotLoggerFormatter)
# add handler to logger object
@ -361,5 +361,5 @@ def Create(inModeStr="BASIC", inLoggerLevel = None):
#mHandlerDumpLogList.setFormatter(mRobotLoggerFormatter)
#mRobotLogger.addHandler(mHandlerDumpLogList)
else:
if lL: lL.warning("Pay attention! Your code has been call SettingsTemplate.Create - since pyOpenRPA v1.2.7 GSettings is creating automatically")
if lL: lL.warning("Внимание! Вручную была вызвана функция SettingsTemplate.Create - начиная с версии pyOpenRPA v1.2.7 глобальный словарь настроек создается автоматически!")
return lResult # return the result dict

@ -1,5 +1,6 @@
from re import I
import subprocess, json, psutil, time, os
import typing
from pyOpenRPA.Tools import CrossOS
if CrossOS.IS_WINDOWS_BOOL: import win32security #CrossOS
if CrossOS.IS_LINUX_BOOL: from simplepam import authenticate #CrossOS
@ -42,7 +43,9 @@ import math
import glob # search the files
import urllib
from . import ServerSettings
from fastapi import FastAPI, Depends
#Единый глобальный словарь (За основу взять из Settings.py)
gSettingsDict = None
@ -120,6 +123,7 @@ def AgentActivityItemReturnGet(inGUIDStr, inCheckIntervalSecFloat = 0.5, inGSett
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inGUIDStr: ГУИД (GUID) активности (ActivityItem)
:param inCheckIntervalSecFloat: Интервал в секундах, с какой частотой выполнять проверку результата. По умолчанию 0.5
:param inTimeoutSecFloat: Время ожидания ответа. Если ответ не поступил, генерация исключения Exception.
:return: Результат выполнения активности. !ВНИМАНИЕ! Возвращаются только то результаты, которые могут быть интерпретированы в JSON формате.
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
@ -436,7 +440,7 @@ def AgentProcessWOExeUpperUserListGet(inHostNameStr, inUserStr, inGSettings = No
"Def":"ProcessWOExeUpperUserListGet", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"])
"ArgList":[], # Args list
"ArgDict":{}, # Args dictionary
"ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgGSettings": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
#Send item in AgentDict for the futher data transmition
@ -619,6 +623,14 @@ def OrchestratorIsAdmin():
elif CrossOS.IS_LINUX_BOOL: return os.getuid()==0
else: return True
def OrchestratorIsCredentialsAsk():
"""L+,W+: Проверить, активирована ли авторизация при переходе к Оркестратору.
:return: True - Активирована; False - Деактивирована
"""
inGSettings = GSettingsGet()
return inGSettings["ServerDict"]["AccessUsers"]["FlagCredentialsAsk"]
def OrchestratorIsInited() -> bool:
"""L+,W+: Проверить, было ли проинициализировано ядро Оркестратора
@ -644,10 +656,14 @@ def OrchestratorRerunAsAdmin():
:return: True - Запущен с правами администратора; False - Не запущен с правами администратора
"""
if not OrchestratorIsAdmin():
# Re-run the program with admin rights
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
if "PYTHON_CONFIGURE" in os.environ:
# Re-run the program with admin rights
ctypes.windll.shell32.ShellExecuteW(None, "runas", "cmd", " ".join(["/C", "cd /d", f'"{os.getcwd()}"', "&&",f'"{os.environ["PYTHON_CONFIGURE"]}"',sys.executable]+sys.argv), None, 1)
else:
# Re-run the program with admin rights
ctypes.windll.shell32.ShellExecuteW(None, "runas", "cmd", " ".join(["/C", "cd /d", f'"{os.getcwd()}"', "&&", sys.executable,sys.argv]), None, 1)
else:
print(f"Уже запущено с правами администратора!")
print(f"Уже запущено с правами администратора!")
def OrchestratorPySearchInit(inGlobPatternStr, inDefStr = None, inDefArgNameGSettingsStr = None, inAsyncInitBool = False, inPackageLevelInt = 0):
"""L+,W+: Выполнить поиск и инициализацию пользовательских .py файлов в Оркестраторе (например панелей управления роботов)
@ -905,9 +921,273 @@ def UACSuperTokenUpdate(inSuperTokenStr, inGSettings=None):
{inSuperTokenStr:{"User":lLoginStr, "Domain":"", "TokenDatetime": datetime.datetime.now(), "FlagDoNotExpire":True}}
)
## GSettings defs
from . import SettingsTemplate
GSettings = SettingsTemplate.Create(inModeStr = "BASIC")
# Modules alias for pyOpenRPA.Orchestrator and pyOpenRPA.Orchestrator.__Orchestrator__
lCurrentModule = sys.modules[__name__]
if __name__ == "pyOpenRPA.Orchestrator" and "pyOpenRPA.Orchestrator.__Orchestrator__" not in sys.modules:
sys.modules["pyOpenRPA.Orchestrator.__Orchestrator__"] = lCurrentModule
if __name__ == "pyOpenRPA.Orchestrator.__Orchestrator__" and "pyOpenRPA.Orchestrator" not in sys.modules:
sys.modules["pyOpenRPA.Orchestrator"] = lCurrentModule
def GSettingsGet(inGSettings=None):
"""L+,W+: Вернуть глобальный словарь настроек Оркестратора. Во время выполнения программы глобальный словарь настроек существует в единственном экземпляре (синглтон)
:param inGSettings: Дополнительный словарь настроек, который необходимо добавить в основной глобальный словарь настроек Оркестратора (синглтон)
:return: Глобальный словарь настроек GSettings
"""
global GSettings # identify the global variable
# Merge dictionaries if some new dictionary has come
if inGSettings is not None and GSettings is not inGSettings:
GSettings = Dictionary.MergeNoException(in1Dict = inGSettings, in2Dict = GSettings)
return GSettings # Return the result
def GSettingsKeyListValueSet(inValue, inKeyList=None, inGSettings = None):
"""L+,W+: Установить значение из глобального словаря настроек Оркестратора GSettings по списку ключей.
Пример: Для того, чтобы установить значение для ключа car в словаре {"complex":{"car":"green"}, "simple":"HELLO"}, необходимо передать список ключей: ["complex", "car"]
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inValue: Значение для установки в глобальный словарь настроек Оркестратора GSettings
:param inKeyList: Список ключей, по адресу которого установить значение в GSettings
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
lDict[inKeyList[-1]] = inValue #Set value
return True
def GSettingsKeyListValueGet(inKeyList=None, inGSettings = None):
"""L+,W+: Получить значение из глобального словаря настроек Оркестратора GSettings по списку ключей.
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inKeyList: Список ключей, по адресу которого получить значение из GSettings
:return: Значение (тип данных определяется форматом хранения в GSettings)
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
return lDict.get(inKeyList[-1],None)
def GSettingsKeyListValueAppend(inValue, inKeyList=None, inGSettings = None):
"""L+,W+: Применить операцию .append к обьекту, расположенному по адресу списка ключей inKeyList в глобальном словаре настроек Оркестратора GSettings. Пример: Добавить значение в конец списка (list).
.. code-block:: python
# ПРИМЕР
from pyOpenRPA import Orchestrator
Orchestrator.GSettingsKeyListValueAppend(
inGSettings = gSettings,
inValue = "NewValue",
inKeyList=["NewKeyDict","NewKeyList"]):
# result inGSettings: {
# ... another keys in gSettings ...,
# "NewKeyDict":{
# "NewKeyList":[
# "NewValue"
# ]
# }
#}
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inValue: Значение для установки в глобальный словарь настроек Оркестратора GSettings
:param inKeyList: Список ключей, по адресу которого выполнить добавление в конец списка (list)
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
lDict[inKeyList[-1]].append(inValue) #Set value
return True
def GSettingsKeyListValueOperatorPlus(inValue, inKeyList=None, inGSettings = None):
"""L+,W+: Применить оператор сложения (+) к обьекту, расположенному по адресу списка ключей inKeyList в глобальном словаре настроек Оркестратора GSettings. Пример: соединить 2 списка (list).
.. code-block:: python
# ПРИМЕР
from pyOpenRPA import Orchestrator
Orchestrator.GSettingsKeyListValueOperatorPlus(
inGSettings = gSettings,
inValue = [1,2,3],
inKeyList=["NewKeyDict","NewKeyList"]):
# result inGSettings: {
# ... another keys in gSettings ...,
# "NewKeyDict":{
# "NewKeyList":[
# "NewValue",
# 1,
# 2,
# 3
# ]
# }
#}
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inValue: Значение для установки в глобальный словарь настроек Оркестратора GSettings
:param inKeyList: Список ключей, по адресу которого выполнить добавление в конец списка (list)
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
lDict[inKeyList[-1]] += inValue #Set value
return True
# # # # # # # # # # # # # # # # # # # # # # #
# OrchestratorWeb defs
# # # # # # # # # # # # # # # # # # # # # # #
def WebUserLoginGet(inAuthTokenStr: str=None) -> str:
"""L+,W+: Получить логин авторизованного пользователя. Если авторизация не производилась - вернуть None
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: Логин пользователя
:rtype: str
"""
isCredentialAsk = OrchestratorIsCredentialsAsk()
if isCredentialAsk:
if inAuthTokenStr is None: raise ConnectionError("Не удается получить токен для авторизации")
else:
if inAuthTokenStr is None: return None
inGS = GSettingsGet() # Get the global settings
return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("User", None)
def WebUserDomainGet(inAuthTokenStr: str=None) -> str:
"""L+,W+: Получить домен авторизованного пользователя. Если авторизация не производилась - вернуть None
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: Домен пользователя
:rtype: str
"""
isCredentialAsk = OrchestratorIsCredentialsAsk()
if isCredentialAsk:
if inAuthTokenStr is None: raise ConnectionError("Не удается получить токен для авторизации")
else:
if inAuthTokenStr is None: return None
inGS = GSettingsGet() # Get the global settings
return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("Domain", None)
def WebUserInfoGet(inAuthTokenStr=None):
"""L+,W+: Информация о пользователе, который отправил HTTP запрос.
:param inRequest: Экземпляр HTTP request. Опционален, если сообщение фиксируется из под потока, который был инициирован запросом пользователя
:return: Сведения в формате {"DomainUpperStr": "PYOPENRPA", "UserNameUpperStr": "IVAN.MASLOV"}
"""
try:
lResultDict = {
"DomainUpperStr": WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper(),
"UserNameUpperStr": WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper()
}
return lResultDict
except Exception as e:
return {"DomainUpperStr": None, "UserNameUpperStr": None}
def WebUserIsSuperToken(inAuthTokenStr: str=None):
"""L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Проверить, авторизован ли HTTP запрос с помощью супер токена (токен, который не истекает).
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: True - является супертокеном; False - не является супертокеном; None - авторизация не производилась
"""
if inAuthTokenStr is None: return None
inGSettings = GSettingsGet() # Get the global settings
lIsSuperTokenBool = False
# Get Flag is supertoken (True|False)
lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("FlagDoNotExpire", False)
return lIsSuperTokenBool
def WebUserUACHierarchyGet(inAuthTokenStr: str=None) -> dict:
"""L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Вернуть словарь доступа UAC в отношении пользователя, который выполнил HTTP запрос inRequest
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: UAC словарь доступа или {}, что означает полный доступ
"""
isCredentialAsk = OrchestratorIsCredentialsAsk()
if isCredentialAsk:
if inAuthTokenStr is None: raise ConnectionError("Не удается получить токен для авторизации")
else:
if inAuthTokenStr is None: return {}
lDomainUpperStr = WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper()
lUserUpperStr = WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper()
if lUserUpperStr is None: return {}
else: return GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("RuleDomainUserDict", {}).get((lDomainUpperStr, lUserUpperStr), {}).get("RoleHierarchyAllowedDict", {})
def WebUserUACCheck(inAuthTokenStr:str=None, inKeyList:list=None) -> bool:
"""L+,W+: Проверить UAC доступ списка ключей для пользователя
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: True - доступ имеется, False - доступа нет
:rtype: bool
"""
isCredentialAsk = OrchestratorIsCredentialsAsk()
# Если авторизации не происходило - супердоступ
if isCredentialAsk:
if inAuthTokenStr is None: return False
else:
if inAuthTokenStr is None: return True
lResult = True # Init flag
lRoleHierarchyDict = WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr) # get the Hierarchy
# Try to get value from key list
lKeyValue = lRoleHierarchyDict # Init the base
for lItem in inKeyList:
if type(lKeyValue) is dict:
if lItem in lKeyValue: # Has key
lKeyValue = lKeyValue[lItem] # Get the value and go to the next loop iteration
else: # Else branch - true or false
if len(lKeyValue)>0: # False - if Dict has some elements
lResult = False # Set the False Flag
else:
lResult = True # Set the True flag
break # Stop the loop
else: # Has element with no detalization - return True
lResult = True # Set the flag
break # Close the loop
return lResult # Return the result
def WebURLIndexChange(inURLIndexStr:str ="/"):
"""L+,W+: Изменить адрес главной страницы Оркестратора. По умолчанию '/'
@ -943,13 +1223,14 @@ def WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentType
Server.BCURLUpdate()
def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings = None, inUACBool = None, inUseCacheBool= False):
def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inExceptionFlagBool=False, inGSettings = None, inUACBool = None, inUseCacheBool= False):
"""L+,W+: Подключить папку к URL.
:param inMethodStr: Метод доступа по URL "GET" || "POST"
:param inURLStr: URL адрес. Пример "/index"
:param inMatchTypeStr: Тип соответсвия строки URL с inURLStr: "BeginWith" || "Contains" || "Equal" || "EqualCase" || "EqualNoParam"
:param inFolderPathStr: Путь к папке на диске, в которой искать файл и возвращать пользователю по HTTP
:param inExceptionFlagBool: Флаг на обработку ошибки. True - показывать ошибку в терминале (остановка инициализации), False - не показывать
:param inUACBool: True - Выполнять проверку прав доступа пользователя перед отправкой ответа; False - не выполнять проверку прав доступа пользователя
:param inUseCacheBool: True - выполнить кэширование страницы, чтобы в следющих запросах открыть быстрее; False - не кэшировать
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
@ -971,7 +1252,7 @@ def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr,
"UseCacheBool": inUseCacheBool
}
inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
Server.BCURLUpdate()
Server.BCURLUpdate(inExceptionFlagBool)
def WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr=None, inGSettings = None, inUACBool = None, inUseCacheBool = False):
@ -1171,242 +1452,30 @@ def WebRequestGet():
if hasattr(lCurrentThread, "request"):
return lCurrentThread.request
def WebUserLoginGet(inAuthTokenStr: str=None) -> str:
"""L+,W+: Получить логин авторизованного пользователя. Если авторизация не производилась - вернуть None
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: Логин пользователя
:rtype: str
"""
if inAuthTokenStr is None: return None
inGS = GSettingsGet() # Get the global settings
return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("User", None)
def WebUserDomainGet(inAuthTokenStr: str=None) -> str:
"""L+,W+: Получить домен авторизованного пользователя. Если авторизация не производилась - вернуть None
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: Домен пользователя
:rtype: str
"""
if inAuthTokenStr is None: return None
inGS = GSettingsGet() # Get the global settings
return inGS.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("Domain", None)
def WebUserInfoGet(inRequest=None):
"""L+,W+: [ПРЕКРАЩЕНИЕ ПОДДЕРЖКИ В 1.3.2, см. WebUserLoginGet, WebUserDomainGet] Информация о пользователе, который отправил HTTP запрос.
:param inRequest: Экземпляр HTTP request. Опционален, если сообщение фиксируется из под потока, который был инициирован запросом пользователя
:return: Сведения в формате {"DomainUpperStr": "PYOPENRPA", "UserNameUpperStr": "IVAN.MASLOV"}
"""
if inRequest is None: inRequest = WebRequestGet()
try:
lDomainUpperStr = inRequest.OpenRPA["Domain"].upper()
lUserUpperStr = inRequest.OpenRPA["User"].upper()
return {"DomainUpperStr": lDomainUpperStr, "UserNameUpperStr": lUserUpperStr}
except Exception as e:
return {"DomainUpperStr": None, "UserNameUpperStr": None}
def WebUserIsSuperToken(inAuthTokenStr: str=None):
"""L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Проверить, авторизован ли HTTP запрос с помощью супер токена (токен, который не истекает).
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: True - является супертокеном; False - не является супертокеном; None - авторизация не производилась
"""
if inAuthTokenStr is None: return None
inGSettings = GSettingsGet() # Get the global settings
lIsSuperTokenBool = False
# Get Flag is supertoken (True|False)
lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inAuthTokenStr, {}).get("FlagDoNotExpire", False)
return lIsSuperTokenBool
def WebUserUACHierarchyGet(inAuthTokenStr: str=None) -> dict:
"""L+,W+: [ИЗМЕНЕНИЕ В 1.3.1] Вернуть словарь доступа UAC в отношении пользователя, который выполнил HTTP запрос inRequest
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: UAC словарь доступа или {}, что означает полный доступ
"""
lDomainUpperStr = WebUserDomainGet(inAuthTokenStr=inAuthTokenStr).upper()
lUserUpperStr = WebUserLoginGet(inAuthTokenStr=inAuthTokenStr).upper()
if lUserUpperStr is None: return {}
else: return GSettingsGet().get("ServerDict", {}).get("AccessUsers", {}).get("RuleDomainUserDict", {}).get((lDomainUpperStr, lUserUpperStr), {}).get("RoleHierarchyAllowedDict", {})
def WebUserUACCheck(inAuthTokenStr:str=None, inKeyList:list=None) -> bool:
"""L+,W+: Проверить UAC доступ списка ключей для пользователя
:param inAuthTokenStr: Токен авторизации пользователя / бота, по умолчанию None (не установлен)
:type inAuthTokenStr: str, опционально
:return: True - доступ имеется, False - доступа нет
:rtype: bool
"""
if inAuthTokenStr is None: return True # Если авторизации не происходило - супердоступ
lResult = True # Init flag
lRoleHierarchyDict = WebUserUACHierarchyGet(inAuthTokenStr=inAuthTokenStr) # get the Hierarchy
# Try to get value from key list
lKeyValue = lRoleHierarchyDict # Init the base
for lItem in inKeyList:
if type(lKeyValue) is dict:
if lItem in lKeyValue: # Has key
lKeyValue = lKeyValue[lItem] # Get the value and go to the next loop iteration
else: # Else branch - true or false
if len(lKeyValue)>0: # False - if Dict has some elements
lResult = False # Set the False Flag
else:
lResult = True # Set the True flag
break # Stop the loop
else: # Has element with no detalization - return True
lResult = True # Set the flag
break # Close the loop
return lResult # Return the result
## GSettings defs
from . import SettingsTemplate
GSettings = SettingsTemplate.Create(inModeStr = "BASIC")
# Modules alias for pyOpenRPA.Orchestrator and pyOpenRPA.Orchestrator.__Orchestrator__
lCurrentModule = sys.modules[__name__]
if __name__ == "pyOpenRPA.Orchestrator" and "pyOpenRPA.Orchestrator.__Orchestrator__" not in sys.modules:
sys.modules["pyOpenRPA.Orchestrator.__Orchestrator__"] = lCurrentModule
if __name__ == "pyOpenRPA.Orchestrator.__Orchestrator__" and "pyOpenRPA.Orchestrator" not in sys.modules:
sys.modules["pyOpenRPA.Orchestrator"] = lCurrentModule
def GSettingsGet(inGSettings=None):
"""L+,W+: Вернуть глобальный словарь настроек Оркестратора. Во время выполнения программы глобальный словарь настроек существует в единственном экземпляре (синглтон)
:param inGSettings: Дополнительный словарь настроек, который необходимо добавить в основной глобальный словарь настроек Оркестратора (синглтон)
:return: Глобальный словарь настроек GSettings
"""
global GSettings # identify the global variable
# Merge dictionaries if some new dictionary has come
if inGSettings is not None and GSettings is not inGSettings:
GSettings = Dictionary.MergeNoException(in1Dict = inGSettings, in2Dict = GSettings)
return GSettings # Return the result
def GSettingsKeyListValueSet(inValue, inKeyList=None, inGSettings = None):
"""L+,W+: Установить значение из глобального словаря настроек Оркестратора GSettings по списку ключей.
Пример: Для того, чтобы установить значение для ключа car в словаре {"complex":{"car":"green"}, "simple":"HELLO"}, необходимо передать список ключей: ["complex", "car"]
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inValue: Значение для установки в глобальный словарь настроек Оркестратора GSettings
:param inKeyList: Список ключей, по адресу которого установить значение в GSettings
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
lDict[inKeyList[-1]] = inValue #Set value
return True
def GSettingsKeyListValueGet(inKeyList=None, inGSettings = None):
"""L+,W+: Получить значение из глобального словаря настроек Оркестратора GSettings по списку ключей.
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inKeyList: Список ключей, по адресу которого получить значение из GSettings
:return: Значение (тип данных определяется форматом хранения в GSettings)
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
return lDict.get(inKeyList[-1],None)
def GSettingsKeyListValueAppend(inValue, inKeyList=None, inGSettings = None):
"""L+,W+: Применить операцию .append к обьекту, расположенному по адресу списка ключей inKeyList в глобальном словаре настроек Оркестратора GSettings. Пример: Добавить значение в конец списка (list).
.. code-block:: python
# ПРИМЕР
from pyOpenRPA import Orchestrator
Orchestrator.GSettingsKeyListValueAppend(
inGSettings = gSettings,
inValue = "NewValue",
inKeyList=["NewKeyDict","NewKeyList"]):
# result inGSettings: {
# ... another keys in gSettings ...,
# "NewKeyDict":{
# "NewKeyList":[
# "NewValue"
# ]
# }
#}
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inValue: Значение для установки в глобальный словарь настроек Оркестратора GSettings
:param inKeyList: Список ключей, по адресу которого выполнить добавление в конец списка (list)
def WebAppGet() -> FastAPI:
"""L+,W+: Вернуть экземпляр веб сервера fastapi.FastAPI (app). Подробнее про app см. https://fastapi.tiangolo.com/tutorial/first-steps/
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
lDict[inKeyList[-1]].append(inValue) #Set value
return True
return Server.app
def GSettingsKeyListValueOperatorPlus(inValue, inKeyList=None, inGSettings = None):
"""L+,W+: Применить оператор сложения (+) к обьекту, расположенному по адресу списка ключей inKeyList в глобальном словаре настроек Оркестратора GSettings. Пример: соединить 2 списка (list).
def WebAuthDefGet():
"""Вернуть функцию авторизации пользователя. Функция может использоваться для доменной авторизации.
.. code-block:: python
.. code-block:: python
# ПРИМЕР
# ПРИМЕР Если требуется авторизация пользователя (получить inAuthTokenStr)
from fastapi import Request
from fastapi.responses import JSONResponse, HTMLResponse
from pyOpenRPA import Orchestrator
Orchestrator.GSettingsKeyListValueOperatorPlus(
inGSettings = gSettings,
inValue = [1,2,3],
inKeyList=["NewKeyDict","NewKeyList"]):
# result inGSettings: {
# ... another keys in gSettings ...,
# "NewKeyDict":{
# "NewKeyList":[
# "NewValue",
# 1,
# 2,
# 3
# ]
# }
#}
:param inGSettings: Глобальный словарь настроек Оркестратора (синглтон)
:param inValue: Значение для установки в глобальный словарь настроек Оркестратора GSettings
:param inKeyList: Список ключей, по адресу которого выполнить добавление в конец списка (list)
@app.post("/url/to/def",response_class=JSONResponse)
async def some_def(inRequest:Request, inAuthTokenStr:str=Depends(Orchestrator.WebAuthDefGet())):
l_input_dict = await inRequest.json()
if lValueStr == None or lValueStr == b"": lValueStr=""
else: lValueStr = lValueStr.decode("utf8")
:return: Функция авторизации
:rtype: def
"""
inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings
if inKeyList is None: inKeyList = []
lDict = inGSettings
for lItem2 in inKeyList[:-1]:
#Check if key - value exists
if lItem2 in lDict:
pass
else:
lDict[lItem2]={}
lDict=lDict[lItem2]
lDict[inKeyList[-1]] += inValue #Set value
return True
return ServerSettings.IdentifyAuthorize
def StorageRobotExists(inRobotNameStr):
"""L+,W+: Проверить, существует ли ключ inRobotNameStr в хранилище пользовательской информации StorageDict (GSettings > StarageDict)

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<!-- Yandex.Metrika counter -->
<script async="" src="https://mc.yandex.ru/metrika/tag.js"></script>
<script type="text/javascript">
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(88079149, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true,
webvisor:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/88079149" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Оркестратор pyOpenRPA</title>
<meta name="description" content="Ведущий RPA разработчик российского программного обеспечения. RPA платформа позволяет решать любые бизнес-задачи. Комплексное решение от компании RPA pyOpenRPA. Первое открытое российское RPA решение для крупного / среднего / малого бизнеса. Доступная автоматизация для каждого." />
<meta name="keywords" content="rpa, программные роботы, автоматизация бизнес-процессов, цифровые сотрудники, виртуальные сотрудники" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/semantic.min.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/orpa/styleset/home.css" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<script
src="/orpa/resources/Web/jQuery/jquery-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="/orpa/resources/Web/Semantic-UI-CSS-master/semantic.min.js"></script>
<script src="/orpa/resources/Web/Handlebars/handlebars-v4.1.2.js"></script>
</head>
<style type="text/css">
h4 {
display: inline-block;
}
button.ui.green.button {
font-family:'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
background: #368279
}
</style>
<body>
{% include 'header.xhtml' %}
<center>
<div class="ui placeholder segment">
<div class="ui icon header">
<i class="user icon"></i>
Для доступа к панели управления, пожалуйста, пройдите авторизацию
</div>
<div class="inline">
<br />
<div class="head"><h4 class="ui header">
<div align="left">Логин от учетной записи</div>
<div class="ui input">
<input type="text" class="headIn" name="uname" style="width: 400px;" autocomplete="off" onchange="inputFormCheck()"/>
</div>
</h4></div>
<br />
<div class="head"><h4 class="ui header">
<div align="left">Пароль от учетной записи</div>
<div class="ui input">
<input type="password" class="headIn" id="pasInp" name="pasw" style="width: 400px;" autocomplete="off" onchange="inputFormCheck()" />
</div>
</h4></div>
<div class="pasChbView" align="left">
<label><input type="checkbox" class="pasChb" /> Показать пароль</label>
</div>
<br />
<button class="ui green button" id="logBtn" onclick="do_auth()" style="width: 200px;">Войти</button>
</div>
</div>
</center>
{% include 'footer.xhtml' %}
<script type="text/javascript">
async function do_auth() {
var uname = document.getElementsByClassName("headIn")[0].value
var psw = document.getElementsByClassName("headIn")[1].value
var authByteArray = (uname+":"+psw).toString('utf8')
getText(authByteArray);
}
async function getText(authByteArray) {
const response = await fetch("/",{
method: "GET",
headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization":btoa(authByteArray)},
credentials: "include"
});
const responseText = await response.text();
if (response.ok) {
location.reload()
}
else {
document.querySelector('html').innerHTML = responseText
}
}
function inputFormCheck() {
var inputForms = document.getElementsByClassName("headIn")
var flag = false
for (var i = 0; i < inputForms.length; i++) {
result = inputForms[i].value.match(/[А-Я]/gi)
if (result) {
flag = true
document.getElementsByClassName("headIn")[i].style.backgroundColor = "#F08080"
}
else {
document.getElementsByClassName("headIn")[i].style.backgroundColor = "white"
}
}
if (flag) {
document.getElementById("logBtn").disabled = true
}
else {
document.getElementById("logBtn").disabled = false
}
}
$('body').on('click', '.pasChb', function(){
if ($(this).is(':checked')){
$('#pasInp').attr('type', 'text');
} else {
$('#pasInp').attr('type', 'password');
}
});
</script>
</body>
<style type="text/css">
.ui.footer.segment {
margin: 0em 0em 0em;
padding: 5.05em 0em;
}
</style>
</html>

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<!-- Yandex.Metrika counter -->
<script async="" src="https://mc.yandex.ru/metrika/tag.js"></script>
<script type="text/javascript">
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(88079149, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true,
webvisor:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/88079149" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Оркестратор pyOpenRPA</title>
<meta name="description" content="Ведущий RPA разработчик российского программного обеспечения. RPA платформа позволяет решать любые бизнес-задачи. Комплексное решение от компании RPA pyOpenRPA. Первое открытое российское RPA решение для крупного / среднего / малого бизнеса. Доступная автоматизация для каждого." />
<meta name="keywords" content="rpa, программные роботы, автоматизация бизнес-процессов, цифровые сотрудники, виртуальные сотрудники" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/semantic.min.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/orpa/styleset/home.css" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<script
src="/orpa/resources/Web/jQuery/jquery-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="/orpa/resources/Web/Semantic-UI-CSS-master/semantic.min.js"></script>
<script src="/orpa/resources/Web/Handlebars/handlebars-v4.1.2.js"></script>
</head>
<style type="text/css">
button.ui.green.button {
font-family:'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
background: #368279
}
</style>
<body>
{% include 'header.xhtml' %}
<br /><br /><br /><br /><br />
<center>
<div class="ui message">
<div class="header">Внимание</div>
<p>{{errorMsg}}</p>
</div>
<button class="ui green button" onclick="document.location='/'" style="width: 200px;">Повторить попытку</button>
</center>
{% include 'footer.xhtml' %}
</body>
<style type="text/css">
.ui.footer.segment {
margin: 10.7em 0em 0em;
padding: 5em 0em;
}
</style>
</html>

@ -1,21 +1,21 @@
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/reset.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/site.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/reset.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/site.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/container.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/grid.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/header.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/image.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/menu.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/container.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/grid.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/header.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/image.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/menu.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/divider.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/dropdown.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/segment.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/button.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/list.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/icon.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/sidebar.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/transition.css">
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/divider.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/dropdown.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/segment.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/button.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/list.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/icon.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/sidebar.css" />
<link rel="stylesheet" type="text/css" href="/orpa/resources/Web/Semantic-UI-CSS-master/components/transition.css" />
<div class="pusher tag-top">

@ -60,6 +60,16 @@ $(document).ready(function() {
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
orpa_workspace_expand=function(){
$("#orpa-workspace")[0].style.setProperty('width', "inherit", "important")
$("#orpa-workspace-expand")[0].style.setProperty('display', "none")
$("#orpa-workspace-compress")[0].style.setProperty('display', "")
}
orpa_workspace_compress=function(){
$("#orpa-workspace")[0].style.setProperty('width', "")
$("#orpa-workspace-expand")[0].style.setProperty('display', "")
$("#orpa-workspace-compress")[0].style.setProperty('display', "none")
}
//For data storage key
mGlobal["DataStorage"] = {}
// Clear the session cookie
@ -541,6 +551,129 @@ $(document).ready(function() {
///Processor functions
///////////////////////////////
mGlobal.Processor = {}
mGlobal.Processor.ServerValueAppend = function(inKeyList,inValue) {
///EXAMPLE
// {
// "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"])
// "ArgList":[], // Args list
// "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary
// "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// }
lData = [
{
"Def":"GSettingsKeyListValueAppend",
"ArgList":[], // Args list
"ArgDict":{"inValue":inValue,"inKeyList":inKeyList}, // Args dictionary
"ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": null // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: '/orpa/api/processor-queue-add',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.ServerValueSet = function(inKeyList,inValue) {
///EXAMPLE
// {
// "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"])
// "ArgList":[], // Args list
// "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary
// "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// }
lData = [
{
"Def":"GSettingsKeyListValueSet",
"ArgList":[], // Args list
"ArgDict":{"inValue":inValue,"inKeyList":inKeyList}, // Args dictionary
"ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": null // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: '/orpa/api/processor-queue-add',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.ServerValueOperatorPlus = function(inKeyList,inValue) {
///EXAMPLE
// {
// "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"])
// "ArgList":[], // Args list
// "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary
// "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// }
lData = [
{
"Def":"GSettingsKeyListValueOperatorPlus",
"ArgList":[], // Args list
"ArgDict":{"inValue":inValue,"inKeyList":inKeyList}, // Args dictionary
"ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
"ArgLogger": null // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
}
]
///Обнулить таблицу
$('.ui.modal.basic .content').html("");
$.ajax({
type: "POST",
url: '/orpa/api/processor-queue-add',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Processor.Send = function(inData) {
///EXAMPLE
// {
// "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"])
// "ArgList":[], // Args list
// "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary
// "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList)
// }
lData = inData
$.ajax({
type: "POST",
url: '/orpa/api/processor-queue-add',
data: JSON.stringify(lData),
success:
function(lData,l2,l3)
{
var lResponseJSON=JSON.parse(lData)
///TODO Show error if exist error
},
dataType: "text"
});
}
mGlobal.Server= {}
mGlobal.Server.JSONGet=function(inMethod, inURL, inDataJSON, inCallback)

@ -81,14 +81,17 @@
<body>
{% include 'header.xhtml' %}
<div class="ui aligned stackable grid container">
<div class="ui aligned stackable grid container" id="orpa-workspace">
<div class="row">
<div class="row" >
<div class="sixteen wide column openrpa-control-panel-general UACClient-pyOpenRPADict-CPKeyDict" style="display:none;" >
<h4 class="ui horizontal divider header" style="margin-bottom:30px;margin-top:30px;">
<i class="clipboard list icon"></i>
Роботы
<div ><i class="clipboard list icon"></i><span>Роботы</span><i class="clipboard list icon"></i></div>
<div id="orpa-workspace-expand" onclick="orpa_workspace_expand()" style="margin-top:10px; color:teal; font-weight:normal; cursor: pointer"><i class="expand icon"></i><span>Развернуть на весь экран</span><i class="expand icon"></i></div>
<div id="orpa-workspace-compress" onclick="orpa_workspace_compress()" style="margin-top:10px; color:teal; font-weight:normal; cursor: pointer; display:none;"><i class="compress icon"></i><span>Свернуть на часть экрана</span><i class="compress icon"></i></div>
</h4>
<div class="openrpa-control-panel"></div>
<script class="openrpa-hidden-control-panel" style="display:none" type="text/x-handlebars-template">
<div class="ui cards">

File diff suppressed because it is too large Load Diff

@ -120,7 +120,7 @@ def UIOSelector_Get_UIOList (inSpecificationList,inElement=None,inFlagRaiseExcep
:type inSpecificationList: list, обязательный
:param inElement: Родительский элемент, от которого выполнить поиск UIO объектов по заданному UIO селектору. Если аргумент не задан, платформа выполнит поиск UIO объектов среди всех доступных приложений windows, которые запущены на текущей сессии
:type inElement: UIO объект, опциональный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай. По умолчанию True
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:return: Список UIO объектов, которые удовлетворяют условиям UIO селектора
'''
@ -128,123 +128,127 @@ def UIOSelector_Get_UIOList (inSpecificationList,inElement=None,inFlagRaiseExcep
inSpecificationList=copy.deepcopy(inSpecificationList)
lResultList=[]
lChildrenList=[]
#Получить родительский объект если на вход ничего не поступило
if inElement is None:
#сформировать спецификацию на получение элемента
lRootElementSpecification=[inSpecificationList[0]]
lRootElementList=PWASpecification_Get_UIO(lRootElementSpecification)
for lRootItem in lRootElementList:
if lRootItem is not None:
lChildrenList.append(lRootItem.wrapper_object())
#Елемент на вход поступил - выполнить его анализ
else:
#Получить список элементов
lElementChildrenList=inElement.children()
#Поступил index - точное добавление
if 'index' in inSpecificationList[0]:
if inSpecificationList[0]['index']<len(lElementChildrenList):
#Получить дочерний элемент - точное добавление
lChildrenList.append(lElementChildrenList[inSpecificationList[0]['index']])
else:
if inFlagRaiseException:
raise ValueError('Object has no children with index: ' + str(inSpecificationList[0]['index']))
#Поступил ctrl_index - точное добавление
elif 'ctrl_index' in inSpecificationList[0]:
if inSpecificationList[0]['ctrl_index']<len(lElementChildrenList):
#Получить дочерний элемент
lChildrenList.append(lElementChildrenList[inSpecificationList[0]['ctrl_index']])
try:
#Получить родительский объект если на вход ничего не поступило
if inElement is None:
#сформировать спецификацию на получение элемента
lRootElementSpecification=[inSpecificationList[0]]
lRootElementList=PWASpecification_Get_UIO(lRootElementSpecification)
for lRootItem in lRootElementList:
if lRootItem is not None:
lChildrenList.append(lRootItem.wrapper_object())
#Елемент на вход поступил - выполнить его анализ
else:
#Получить список элементов
lElementChildrenList=inElement.children()
#Поступил index - точное добавление
if 'index' in inSpecificationList[0]:
if inSpecificationList[0]['index']<len(lElementChildrenList):
#Получить дочерний элемент - точное добавление
lChildrenList.append(lElementChildrenList[inSpecificationList[0]['index']])
else:
if inFlagRaiseException:
raise ValueError('Object has no children with index: ' + str(inSpecificationList[0]['index']))
#Поступил ctrl_index - точное добавление
elif 'ctrl_index' in inSpecificationList[0]:
if inSpecificationList[0]['ctrl_index']<len(lElementChildrenList):
#Получить дочерний элемент
lChildrenList.append(lElementChildrenList[inSpecificationList[0]['ctrl_index']])
else:
if inFlagRaiseException:
raise ValueError('Object has no children with index: ' + str(inSpecificationList[0]['ctrl_index']))
#Если нет точного обозначения элемента
else:
if inFlagRaiseException:
raise ValueError('Object has no children with index: ' + str(inSpecificationList[0]['ctrl_index']))
#Если нет точного обозначения элемента
lFlagGoCheck=True
#Учесть поле depth_start (если указано)
if 'depth_start' in inSpecificationList[0]:
if inSpecificationList[0]["depth_start"]>1:
lFlagGoCheck=False
#Циклический обход по детям, на предмет соответствия всем условиям
for lChildrenItem in lElementChildrenList:
#Обработка глубины depth (рекурсивный вызов для всех детей с занижением индекса глубины)
#По умолчанию значение глубины 1
if 'depth_end' in inSpecificationList[0]:
if inSpecificationList[0]['depth_end']>1:
#Подготовка новой версии спецификации
lChildrenItemNewSpecificationList=inSpecificationList.copy()
lChildrenItemNewSpecificationList[0]=lChildrenItemNewSpecificationList[0].copy()
lChildrenItemNewSpecificationList[0]["depth_end"]=lChildrenItemNewSpecificationList[0]["depth_end"]-1
if 'depth_start' in lChildrenItemNewSpecificationList[0]:
lChildrenItemNewSpecificationList[0]["depth_start"]=lChildrenItemNewSpecificationList[0]["depth_start"]-1
#Циклический вызов для всех детей со скорректированной спецификацией
lResultList.extend(UIOSelector_Get_UIOList(lChildrenItemNewSpecificationList,lChildrenItem,inFlagRaiseException))
#Фильтрация
#TODO Сделать поддержку этих атрибутов для первого уровня селектора
if lFlagGoCheck:
lFlagAddChild=True
#Фильтрация по title
if 'title' in inSpecificationList[0]:
if lChildrenItem.element_info.name != inSpecificationList[0]["title"]:
lFlagAddChild=False
#Фильтрация по title_re (regexp)
if 'title_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["title_re"],lChildrenItem.element_info.name) is None:
lFlagAddChild=False
#Фильтрация по rich_text
if 'rich_text' in inSpecificationList[0]:
if lChildrenItem.element_info.rich_text != inSpecificationList[0]["rich_text"]:
lFlagAddChild=False
#Фильтрация по rich_text_re (regexp)
if 'rich_text_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["rich_text_re"],lChildrenItem.element_info.rich_text) is None:
lFlagAddChild=False
#Фильтрация по class_name
if 'class_name' in inSpecificationList[0]:
if lChildrenItem.element_info.class_name != inSpecificationList[0]["class_name"]:
lFlagAddChild=False
#Фильтрация по class_name_re (regexp)
if 'class_name_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["class_name_re"],lChildrenItem.element_info.class_name) is None:
lFlagAddChild=False
#Фильтрация по friendly_class_name
if 'friendly_class_name' in inSpecificationList[0]:
if lChildrenItem.friendly_class_name() != inSpecificationList[0]["friendly_class_name"]:
lFlagAddChild=False
#Фильтрация по friendly_class_name_re (regexp)
if 'friendly_class_name_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["friendly_class_name_re"],lChildrenItem.friendly_class_name) is None:
lFlagAddChild=False
#Фильтрация по control_type
if 'control_type' in inSpecificationList[0]:
if lChildrenItem.element_info.control_type != inSpecificationList[0]["control_type"]:
lFlagAddChild=False
#Фильтрация по control_type_re (regexp)
if 'control_type_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["control_type_re"],lChildrenItem.element_info.control_type) is None:
lFlagAddChild=False
#Фильтрация по is_enabled (bool)
if 'is_enabled' in inSpecificationList[0]:
if lChildrenItem.is_enabled()!=inSpecificationList[0]["is_enabled"]:
lFlagAddChild=False
#Фильтрация по is_visible (bool)
if 'is_visible' in inSpecificationList[0]:
if lChildrenItem.is_visible()!=inSpecificationList[0]["is_visible"]:
lFlagAddChild=False
#####
#Все проверки пройдены - флаг добавления
if lFlagAddChild:
lChildrenList.append(lChildrenItem)
#Выполнить рекурсивный вызов (уменьшение количества спецификаций), если спецификация больше одного элемента
#????????Зачем в условии ниже is not None ???????????
if len(inSpecificationList)>1 and len(lChildrenList)>0:
#Вызвать рекурсивно функцию получения следующего объекта, если в спецификации есть следующий объект
for lChildElement in lChildrenList:
lResultList.extend(UIOSelector_Get_UIOList(inSpecificationList[1:],lChildElement,inFlagRaiseException))
else:
lFlagGoCheck=True
#Учесть поле depth_start (если указано)
if 'depth_start' in inSpecificationList[0]:
if inSpecificationList[0]["depth_start"]>1:
lFlagGoCheck=False
#Циклический обход по детям, на предмет соответствия всем условиям
for lChildrenItem in lElementChildrenList:
#Обработка глубины depth (рекурсивный вызов для всех детей с занижением индекса глубины)
#По умолчанию значение глубины 1
if 'depth_end' in inSpecificationList[0]:
if inSpecificationList[0]['depth_end']>1:
#Подготовка новой версии спецификации
lChildrenItemNewSpecificationList=inSpecificationList.copy()
lChildrenItemNewSpecificationList[0]=lChildrenItemNewSpecificationList[0].copy()
lChildrenItemNewSpecificationList[0]["depth_end"]=lChildrenItemNewSpecificationList[0]["depth_end"]-1
if 'depth_start' in lChildrenItemNewSpecificationList[0]:
lChildrenItemNewSpecificationList[0]["depth_start"]=lChildrenItemNewSpecificationList[0]["depth_start"]-1
#Циклический вызов для всех детей со скорректированной спецификацией
lResultList.extend(UIOSelector_Get_UIOList(lChildrenItemNewSpecificationList,lChildrenItem,inFlagRaiseException))
#Фильтрация
#TODO Сделать поддержку этих атрибутов для первого уровня селектора
if lFlagGoCheck:
lFlagAddChild=True
#Фильтрация по title
if 'title' in inSpecificationList[0]:
if lChildrenItem.element_info.name != inSpecificationList[0]["title"]:
lFlagAddChild=False
#Фильтрация по title_re (regexp)
if 'title_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["title_re"],lChildrenItem.element_info.name) is None:
lFlagAddChild=False
#Фильтрация по rich_text
if 'rich_text' in inSpecificationList[0]:
if lChildrenItem.element_info.rich_text != inSpecificationList[0]["rich_text"]:
lFlagAddChild=False
#Фильтрация по rich_text_re (regexp)
if 'rich_text_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["rich_text_re"],lChildrenItem.element_info.rich_text) is None:
lFlagAddChild=False
#Фильтрация по class_name
if 'class_name' in inSpecificationList[0]:
if lChildrenItem.element_info.class_name != inSpecificationList[0]["class_name"]:
lFlagAddChild=False
#Фильтрация по class_name_re (regexp)
if 'class_name_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["class_name_re"],lChildrenItem.element_info.class_name) is None:
lFlagAddChild=False
#Фильтрация по friendly_class_name
if 'friendly_class_name' in inSpecificationList[0]:
if lChildrenItem.friendly_class_name() != inSpecificationList[0]["friendly_class_name"]:
lFlagAddChild=False
#Фильтрация по friendly_class_name_re (regexp)
if 'friendly_class_name_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["friendly_class_name_re"],lChildrenItem.friendly_class_name) is None:
lFlagAddChild=False
#Фильтрация по control_type
if 'control_type' in inSpecificationList[0]:
if lChildrenItem.element_info.control_type != inSpecificationList[0]["control_type"]:
lFlagAddChild=False
#Фильтрация по control_type_re (regexp)
if 'control_type_re' in inSpecificationList[0]:
if re.fullmatch(inSpecificationList[0]["control_type_re"],lChildrenItem.element_info.control_type) is None:
lFlagAddChild=False
#Фильтрация по is_enabled (bool)
if 'is_enabled' in inSpecificationList[0]:
if lChildrenItem.is_enabled()!=inSpecificationList[0]["is_enabled"]:
lFlagAddChild=False
#Фильтрация по is_visible (bool)
if 'is_visible' in inSpecificationList[0]:
if lChildrenItem.is_visible()!=inSpecificationList[0]["is_visible"]:
lFlagAddChild=False
#####
#Все проверки пройдены - флаг добавления
if lFlagAddChild:
lChildrenList.append(lChildrenItem)
#Выполнить рекурсивный вызов (уменьшение количества спецификаций), если спецификация больше одного элемента
#????????Зачем в условии ниже is not None ???????????
if len(inSpecificationList)>1 and len(lChildrenList)>0:
#Вызвать рекурсивно функцию получения следующего объекта, если в спецификации есть следующий объект
for lChildElement in lChildrenList:
lResultList.extend(UIOSelector_Get_UIOList(inSpecificationList[1:],lChildElement,inFlagRaiseException))
else:
lResultList.extend(lChildrenList)
#Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова)
if inElement is None and len(lResultList)==0 and inFlagRaiseException:
raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector")
return lResultList
lResultList.extend(lChildrenList)
#Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова)
if inElement is None and len(lResultList)==0 and inFlagRaiseException:
raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector")
return lResultList
except Exception as e:
if inFlagRaiseException: raise e
else: return []
#old:PywinautoExtElementGet
def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException=True):
@ -268,16 +272,13 @@ def UIOSelector_Get_UIO (inSpecificationList,inElement=None,inFlagRaiseException
'''
lResult=None
#Получить родительский объект если на вход ничего не поступило
lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement,False)
lResultList=UIOSelector_Get_UIOList(inSpecificationList,inElement,inFlagRaiseException)
if len(lResultList)>0:
lResult=lResultList[0]
#Условие, если результирующий список пустой и установлен флаг создания ошибки (и inElement is None - не следствие рекурсивного вызова)
if lResult is None and inFlagRaiseException:
raise pywinauto.findwindows.ElementNotFoundError("Robot can't find element by the UIOSelector")
return lResult
#old:-
def UIOSelector_Exist_Bool (inUIOSelector):
def UIOSelector_Exist_Bool (inUIOSelector, inFlagRaiseException=True):
'''L-,W+: Проверить существование хотя бы 1-го UIO объекта по заданному UIO селектору
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -292,6 +293,8 @@ def UIOSelector_Exist_Bool (inUIOSelector):
:param inUIOSelector: UIO Селектор, который определяет критерии поиска UIO объектов
:type inUIOSelector: list, обязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:return: True - существует хотя бы 1 UIO объект. False - не существует ни одного UIO объекта по заданному UIO селектору
'''
lResult=False
@ -299,7 +302,9 @@ def UIOSelector_Exist_Bool (inUIOSelector):
lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector)
if lSafeOtherProcess is None:
#Получить родительский объект если на вход ничего не поступило
lResultList=UIOSelector_Get_UIOList(inUIOSelector, None, False)
try:
lResultList=UIOSelector_Get_UIOList(inUIOSelector, None, inFlagRaiseException)
except pywinauto.findwindows.ElementNotFoundError: return False
if len(lResultList)>0:
lResult=True
else:
@ -319,7 +324,7 @@ def UIOSelector_Exist_Bool (inUIOSelector):
return lResult
#old: -
def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False):
def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False, inFlagRaiseException=True):
'''L-,W+: Ожидать появление хотя бы 1-го / всех UIO объектов по заданным UIO селекторам
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -342,6 +347,8 @@ def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0
:type inSpecificationListList: list, обязательный
:param inWaitSecs: Количество секунд, которые отвести на ожидание UIO объектов. По умолчанию 24 часа (86400 секунд)
:type inWaitSecs: float, необязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:param inFlagWaitAllInMoment: True - Ожидать до того момента, пока не появятся все запрашиваемые UIO объекты на рабочей области
:return: Список индексов, которые указывают на номер входящих UIO селекторов, которые были обнаружены на рабочей области. Пример: [0,2]
'''
@ -356,7 +363,7 @@ def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0
#Итерация проверки
lIndex = 0
for lItem in inSpecificationListList:
lItemResultFlag=UIOSelector_Exist_Bool(lItem)
lItemResultFlag=UIOSelector_Exist_Bool(lItem, inFlagRaiseException=True)
#Если обнаружен элемент - добавить его индекс в массив
if lItemResultFlag:
lResultList.append(lIndex)
@ -376,7 +383,7 @@ def UIOSelectorsSecs_WaitAppear_List (inSpecificationListList,inWaitSecs=86400.0
return lResultList
#old: -
def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False):
def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=86400.0,inFlagWaitAllInMoment=False, inFlagRaiseException=True):
'''L-,W+: Ожидать исчезновение хотя бы 1-го / всех UIO объектов по заданным UIO селекторам
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -399,6 +406,8 @@ def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=8640
:type inSpecificationListList: list, обязательный
:param inWaitSecs: Количество секунд, которые отвести на ожидание исчезновения UIO объектов. По умолчанию 24 часа (86400 секунд)
:type inWaitSecs: float, необязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:param inFlagWaitAllInMoment: True - Ожидать до того момента, пока не исчезнут все запрашиваемые UIO объекты на рабочей области
:return: Список индексов, которые указывают на номер входящих UIO селекторов, которые были обнаружены на рабочей области. Пример: [0,2]
'''
@ -414,7 +423,7 @@ def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=8640
#Итерация проверки
lIndex = 0
for lItem in inSpecificationListList:
lItemResultFlag=UIOSelector_Exist_Bool(lItem)
lItemResultFlag=UIOSelector_Exist_Bool(lItem,inFlagRaiseException=inFlagRaiseException)
#Если обнаружен элемент - добавить его индекс в массив
if not lItemResultFlag:
lResultList.append(lIndex)
@ -434,7 +443,7 @@ def UIOSelectorsSecs_WaitDisappear_List (inSpecificationListList,inWaitSecs=8640
return lResultList
#old: -
def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs):
def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs, inFlagRaiseException=True):
'''L-,W+: Ожидать появление 1-го UIO объекта по заданному UIO селектору
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -451,16 +460,18 @@ def UIOSelectorSecs_WaitAppear_Bool (inSpecificationList,inWaitSecs):
:type inSpecificationList: list, обязательный
:param inWaitSecs: Количество секунд, которые отвести на ожидание UIO объекта. По умолчанию 24 часа (86400 секунд)
:type inWaitSecs: float, необязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:return: True - UIO объект был обнаружен. False - обратная ситуациая
'''
lWaitAppearList=UIOSelectorsSecs_WaitAppear_List([inSpecificationList],inWaitSecs)
lWaitAppearList=UIOSelectorsSecs_WaitAppear_List([inSpecificationList],inWaitSecs, inFlagRaiseException=inFlagRaiseException)
lResult=False
if len(lWaitAppearList)>0:
lResult=True
return lResult
#old name - -
def UIOSelectorSecs_WaitDisappear_Bool (inSpecificationList,inWaitSecs):
def UIOSelectorSecs_WaitDisappear_Bool (inSpecificationList,inWaitSecs,inFlagRaiseException=True):
'''L-,W+: Ожидать исчезновение 1-го UIO объекта по заданному UIO селектору
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -477,9 +488,11 @@ def UIOSelectorSecs_WaitDisappear_Bool (inSpecificationList,inWaitSecs):
:type inSpecificationList: list, обязательный
:param inWaitSecs: Количество секунд, которые отвести на исчезновение UIO объекта. По умолчанию 24 часа (86400 секунд)
:type inWaitSecs: float, необязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:return: True - UIO объект был обнаружен. False - обратная ситуациая
'''
lWaitDisappearList=UIOSelectorsSecs_WaitDisappear_List([inSpecificationList],inWaitSecs)
lWaitDisappearList=UIOSelectorsSecs_WaitDisappear_List([inSpecificationList],inWaitSecs,inFlagRaiseException=inFlagRaiseException)
lResult=False
if len(lWaitDisappearList)>0:
lResult=True
@ -908,7 +921,7 @@ def UIO_GetCtrlIndex_Int(inElement):
return lResult
#old: - PywinautoExtElementsGetInfo
def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None):
def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None, inFlagRaiseException=True):
"""L-,W+: Техническая функция: Получить список параметров последних уровней UIO селектора по UIO объектам, которые удовлетворяют входящим inUIOSelector, поиск по которым будет производится от уровня inElement.
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -925,13 +938,15 @@ def UIOSelector_Get_UIOInfoList (inUIOSelector, inElement=None):
:type inUIOSelector: list, обязательный
:param inElement: UIO объект, от которого выполнить поиск дочерних UIO объектов по UIO селектору inUIOSelector. По умолчанию None - поиск среди всех приложений.
:type inElement: UIO объект, необязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:return: dict, пример: {"title":None,"rich_text":None,"process_id":None,"process":None,"handle":None,"class_name":None,"control_type":None,"control_id":None,"rectangle":{"left":None,"top":None,"right":None,"bottom":None}, 'runtime_id':None}
"""
#Check the bitness
lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector)
if lSafeOtherProcess is None:
#Получить родительский объект если на вход ничего не поступило
lResultList=UIOSelector_Get_UIOList(inUIOSelector, inElement)
lResultList=UIOSelector_Get_UIOList(inUIOSelector, inElement, inFlagRaiseException)
lIterator = 0
for lItem in lResultList:
lResultList[lIterator]=UIOEI_Convert_UIOInfo(lResultList[lIterator].element_info)
@ -985,7 +1000,7 @@ def UIOSelector_TryRestore_Dict(inSpecificationList):
return lResult
#old: - ElementActionGetList
def UIOSelector_Get_UIOActivityList (inUIOSelector):
def UIOSelector_Get_UIOActivityList (inUIOSelector,inFlagRaiseException=True):
"""L-,W+: Получить список доступных действий/функций по UIO селектору inUIOSelector. Описание возможных активностей см. ниже.
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -1000,12 +1015,14 @@ def UIOSelector_Get_UIOActivityList (inUIOSelector):
:param inUIOSelector: UIO селектор, который определяет UIO объект, для которого будет представлен перечень доступных активностей.
:type inUIOSelector: list, обязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
"""
#Check the bitness
lSafeOtherProcess = UIOSelector_SafeOtherGet_Process(inUIOSelector)
if lSafeOtherProcess is None:
#Получить объект
lObject=UIOSelector_Get_UIO(inUIOSelector)
lObject=UIOSelector_Get_UIO(inUIOSelector,inFlagRaiseException=inFlagRaiseException)
lActionList=dir(lObject)
lResult=dir(lObject)
#Выполнить чистку списка от неактуальных методов
@ -1033,7 +1050,7 @@ def UIOSelector_Get_UIOActivityList (inUIOSelector):
return lResult
#old: - ElementRunAction
def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList=None, inkwArgumentObject=None):
def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inFlagRaiseException=True, inArgumentList=None, inkwArgumentObject=None):
"""L-,W+: Выполнить активность inActionName над UIO объектом, полученным с помощью UIO селектора inUIOSelector. Описание возможных активностей см. ниже.
!ВНИМАНИЕ! ДАННАЯ ФУНКЦИОНАЛЬНОСТЬ В АВТОМАТИЧЕСКОМ РЕЖИМЕ ПОДДЕРЖИВАЕТ ВСЕ РАЗРЯДНОСТИ ПРИЛОЖЕНИЙ (32|64), КОТОРЫЕ ЗАПУЩЕНЫ В СЕСИИ. PYTHON x64 ИМЕЕТ ВОЗМОЖНОСТЬ ВЗЗАИМОДЕЙСТВИЯ С x32 UIO ОБЪЕКТАМИ, НО МЫ РЕКОМЕНДУЕМ ДОПОЛНИТЕЛЬНО ИСПОЛЬЗОВАТЬ ИНТЕРПРЕТАТОР PYTHON x32 (ПОДРОБНЕЕ СМ. ФУНКЦИЮ Configure())
@ -1050,6 +1067,8 @@ def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList=
:type inUIOSelector: list, обязательный
:param inActionName: наименование активности, которую требуется выполнить над UIO объектом
:type inActionName: str, обязательный
:param inFlagRaiseException: True - формировать ошибку exception, если платформа не обнаружина ни одного UIO объекта по заданному UIO селектору. False - обратный случай (может привести к ошибочным результатам). По умолчанию True.
:type inFlagRaiseException: bool, опциональный
:param inArgumentList: список передаваемых неименованных аргументов в функцию inActionName
:type inArgumentList: list, необязательный
:param inkwArgumentObject: словарь передаваемых именованных аргументов в функцию inActionName
@ -1064,7 +1083,7 @@ def UIOSelectorUIOActivity_Run_Dict(inUIOSelector, inActionName, inArgumentList=
#Run activity if SafeOtherProcess is None
if lSafeOtherProcess is None:
#Определить объект
lObject=UIOSelector_Get_UIO(inUIOSelector)
lObject=UIOSelector_Get_UIO(inUIOSelector,inFlagRaiseException=inFlagRaiseException)
#Получить метод для вызова
lFunction = getattr(lObject, inActionName)
#Выполнить действие

@ -1,8 +1,10 @@
from selenium import *
from selenium import webdriver, common
from selenium.webdriver.common.by import By
from selenium.common.exceptions import JavascriptException
import os
import sys
import json
from pyOpenRPA.Tools import CrossOS
import time
@ -14,8 +16,8 @@ UIO_WAIT_INTERVAL_SEC_FLOAT = 1.0
gBrowser:webdriver.Chrome = None
def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = None, inExtensionPathList:list = None, inProfilePathStr:str=None) -> webdriver.Chrome:
"""L+,W+: Выполнить запуск браузера Chrome. Если вы скачали pyOpenRPA вместе с репозиторием, то будет использоваться встроенный браузер Google Chrome. Если установка pyOpenRPA производилась другим способом, то требуется указать расположение браузера Google Chrome и соответствующего WebDriver.
def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = None, inExtensionPathList:list = None, inProfilePathStr:str=None, inSaveAsPDFBool = False, inSavefileDefaultDirStr = None) -> webdriver.Chrome:
"""L+,W+: Выполнить запуск браузера Chrome. Если вы скачали pyOpenRPA вместе с репозиторием, то будет использоваться встроенный браузер Google Chrome. Если установка pyOpenRPA производилась другим способом, то требуется указать расположение браузера Google Chrome и соответствующего WebDriver
.. code-block:: python
@ -32,6 +34,10 @@ def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = N
:type inExtensionPathList: list, опционально
:param inProfilePathStr: Путь, по которому выполнить сохранения профиля Chrome (история, куки и т.д.), по умолчанию None (профиль не сохраняется)
:type inProfilePathStr: str, опционально
:param inSaveAsPDFBool: Флаг, который обеспечивает настройки окна печати вэб-страницы как "Сохранить как PDF", по умолчанию False (настройки по умолчанию)
:type inSaveAsPDFBool: bool, опционально
:param inSavefileDefaultDirStr: Путь, по которому выполнить сохранения файла (после работы с окном печать вэб-страницы браузера) (история, куки и т.д.), по умолчанию None (файл не сохраняется)
:type inSavefileDefaultDirStr: str, опционально
:return: Объект браузера Google Chrome
:rtype: webdriver.Chrome
"""
@ -54,8 +60,31 @@ def BrowserChromeStart(inDriverExePathStr:str = None, inChromeExePathStr:str = N
if CrossOS.IS_WINDOWS_BOOL: inChromeExePathStr = os.path.join(lResourcePathStr, "WChrome64-840414730", "App", "Chrome-bin", "chrome.exe")
elif CrossOS.IS_LINUX_BOOL: inChromeExePathStr = os.path.join(lResourcePathStr, "LChrome64-10305060114", "data", "chrome")
if inExtensionPathList == None: inExtensionPathList = []
# Set full path to exe of the chrome
# Установка настроек окна печати, если необходимо
lWebDriverChromeOptionsInstance = webdriver.ChromeOptions()
if inSaveAsPDFBool == True and inSavefileDefaultDirStr is not None:
print_settings = {
"recentDestinations": [{
"id": "Save as PDF",
"origin": "local",
"account": "",
}],
"selectedDestinationId": "Save as PDF",
"version": 2, # в chrome - это номер варинта "сохранить как PDF"
"isHeaderFooterEnabled": False, # хедеры HTML на странице
"isLandscapeEnabled": False # ориентация (True - альбомная)
}
prefs = {'printing.print_preview_sticky_settings.appState': json.dumps(print_settings),
"download.prompt_for_download": False,
"profile.default_content_setting_values.automatic_downloads": 1,
"download.default_directory": inSavefileDefaultDirStr,
"savefile.default_directory": inSavefileDefaultDirStr,
"download.directory_upgrade": True,
"safebrowsing.enabled": True}
lWebDriverChromeOptionsInstance.add_experimental_option('prefs', prefs)
lWebDriverChromeOptionsInstance.add_argument('--kiosk-printing')
# Set full path to exe of the chrome
lWebDriverChromeOptionsInstance.binary_location = inChromeExePathStr
#lWebDriverChromeOptionsInstance2 = webdriver.ChromeOptions()
if inProfilePathStr is not None:
@ -94,6 +123,8 @@ def BrowserChange(inBrowser):
def PageOpen(inURLStr: str):
"""L+,W+: Открыть страницу inURLStr в браузере и дождаться ее загрузки.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
# UIWeb: Взаимодействие с ui web
@ -107,9 +138,30 @@ def PageOpen(inURLStr: str):
"""
global gBrowser
if gBrowser is not None: gBrowser.get(inURLStr)
def PagePrint():
"""L+,W+: Открыть окно печати браузера.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
# UIWeb: Взаимодействие с ui web
from pyOpenRPA.Robot import UIWeb
import time
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
time.sleep(1)
UIWeb.PagePrint()
UIWeb.BrowserClose()
"""
PageJSExecute(inJSStr=f"window.print()")
def PageScrollTo(inVerticalPxInt=0, inHorizontalPxInt=0):
"""L+,W+: Выполнить прокрутку страницы (по вертикали или по горизонтали)
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -129,6 +181,8 @@ def PageScrollTo(inVerticalPxInt=0, inHorizontalPxInt=0):
def PageJSExecute(inJSStr, *inArgList):
"""L+,W+: Отправить на выполнение на сторону браузера код JavaScript.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
!ВНИМАНИЕ! Данная функция поддерживает передачу переменных в область кода JavaScript (*inArgList). Обратиться к переданным переменным из JavaScript можно с помощью ключевого слова: arguments[i], где i - это порядковый номер переданной переменной
@ -169,6 +223,8 @@ def BrowserClose():
def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list:
"""L+,W+: Получить список UIO объектов по UIO селектору.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -176,7 +232,7 @@ def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list:
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOList = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)
UIWeb.BrowserClose()
@ -204,6 +260,8 @@ def UIOSelectorList(inUIOSelectorStr, inUIO=None) -> list:
def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list:
"""L+,W+: Получить UIO объект по UIO селектору.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -211,7 +269,7 @@ def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list:
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorFirst(inUIOSelectorStr = lUIOSelectorStr)
UIWeb.BrowserClose()
@ -229,6 +287,8 @@ def UIOSelectorFirst(inUIOSelectorStr, inUIO=None) -> list:
def UIOTextGet(inUIO) -> str:
"""L+,W+: Получить текст UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -236,7 +296,7 @@ def UIOTextGet(inUIO) -> str:
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
lTextStr = UIWeb.UIOTextGet(inUIO=lUIO)
UIWeb.BrowserClose()
@ -250,6 +310,8 @@ def UIOTextGet(inUIO) -> str:
def UIOAttributeGet(inUIO, inAttributeStr) -> str:
"""L+,W+: Получить обычный (нестилевой) атрибут у UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -257,7 +319,7 @@ def UIOAttributeGet(inUIO, inAttributeStr) -> str:
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIWeb.UIOAttributeGet(inUIO=lUIO, inAttributeStr = "href")
UIWeb.BrowserClose()
@ -273,6 +335,8 @@ def UIOAttributeGet(inUIO, inAttributeStr) -> str:
def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str:
"""L+,W+: Получить стилевой атрибут у UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -280,7 +344,7 @@ def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str:
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIWeb.UIOAttributeStyleGet(inUIO=lUIO, inAttributeStr = "href")
UIWeb.BrowserClose()
@ -296,6 +360,8 @@ def UIOAttributeStyleGet(inUIO, inAttributeStr) -> str:
def UIOAttributeSet(inUIO, inAttributeStr, inValue):
"""L+,W+: Установить обычный (нестилевой) атрибут у UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -303,7 +369,7 @@ def UIOAttributeSet(inUIO, inAttributeStr, inValue):
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIWeb.UIOAttributeSet(inUIO=lUIO, inAttributeStr = "href", inValue = "https://mail.ru")
UIWeb.BrowserClose()
@ -321,6 +387,8 @@ def UIOAttributeSet(inUIO, inAttributeStr, inValue):
def UIOAttributeRemove(inUIO, inAttributeStr):
"""L+,W+: Удалить обычный (нестилевой) атрибут у UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -328,7 +396,7 @@ def UIOAttributeRemove(inUIO, inAttributeStr):
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIWeb.UIOAttributeRemove(lUIO, "href")
UIWeb.BrowserClose()
@ -344,6 +412,8 @@ def UIOAttributeRemove(inUIO, inAttributeStr):
def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue):
"""L+,W+: Установить стилевой атрибут у UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -351,7 +421,7 @@ def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue):
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIWeb.UIOAttributeStyleSet(inUIO=lUIO, inAttributeStr = "color", inValue = "grey")
UIWeb.BrowserClose()
@ -369,6 +439,8 @@ def UIOAttributeStyleSet(inUIO, inAttributeStr, inValue):
def UIOAttributeStyleRemove(inUIO, inAttributeStr:str):
"""L+,W+: Удалить стилевой атрибут у UI элемента.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -376,7 +448,7 @@ def UIOAttributeStyleRemove(inUIO, inAttributeStr:str):
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIWeb.UIOAttributeStyleRemove(lUIO, "color")
UIWeb.BrowserClose()
@ -392,6 +464,8 @@ def UIOAttributeStyleRemove(inUIO, inAttributeStr:str):
def UIOClick(inUIO):
"""L+,W+: Выполнить нажатие по элементу inUIO.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -399,7 +473,7 @@ def UIOClick(inUIO):
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIO = UIWeb.UIOSelectorList(inUIOSelectorStr = lUIOSelectorStr)[0]
UIOClick(inUIO = lUIO)
UIWeb.BrowserClose()
@ -411,6 +485,8 @@ def UIOClick(inUIO):
def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDurationSecFloat:float=3.0, inColorStr:str="green"):
"""L+,W+: Выполнить подсвечивание UI элемента с селектором inUIOSelectorStr.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -418,7 +494,7 @@ def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDuration
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
UIWeb.UIOSelectorHighlight(inUIOSelectorStr = lUIOSelectorStr)
UIWeb.BrowserClose()
@ -467,6 +543,8 @@ def UIOSelectorHighlight(inUIOSelectorStr: str, inIsFirst:bool=False, inDuration
def UIOSelectorClick(inUIOSelectorStr: str):
"""L+,W+: Выполнить нажатие по элементу с селектором inUIOSelectorStr.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -474,25 +552,56 @@ def UIOSelectorClick(inUIOSelectorStr: str):
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
UIWeb.UIOSelectorClick(inUIOSelectorStr = lUIOSelectorStr)
UIWeb.BrowserClose()
:param inUIOSelectorStr: XPATH или CSS селектор UI элемента на web странице. Подсказки по CSS: https://devhints.io/css Подсказки по XPath: https://devhints.io/xpath
:type inUIOSelectorStr: str
"""
PageJSExecute(inJSStr=f"document.querySelector('{inUIOSelectorStr}').click()")
if UIOSelectorDetect(inUIOSelectorStr=inUIOSelectorStr) == "CSS":
PageJSExecute(inJSStr=f"document.querySelector('{inUIOSelectorStr}').click()")
else:
PageJSExecute(inJSStr=f"document.evaluate('{inUIOSelectorStr}', document, null , XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click()")
def UIOSelectorSetValue(inUIOSelectorStr: str, inValue: str):
"""L+,W+: Установить значение элемента с селектором inUIOSelectorStr.
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
# UIWeb: Взаимодействие с ui web
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://www.google.com/")
lUIOSelectorStr = "/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input"
lValue = "pyOpenRPA"
UIWeb.UIOSelectorSetValue(inUIOSelectorStr = lUIOSelectorStr, inValue = lValue)
UIWeb.BrowserClose()
:param inUIOSelectorStr: XPATH или CSS селектор UI элемента на web странице. Подсказки по CSS: https://devhints.io/css Подсказки по XPath: https://devhints.io/xpath
:type inUIOSelectorStr: str
:param inValue: Значение, которое необходимо установить
:type inValue: str
"""
if UIOSelectorDetect(inUIOSelectorStr=inUIOSelectorStr) == "CSS":
PageJSExecute(inJSStr=f"document.querySelector('{inUIOSelectorStr}').value='{inValue}'")
else:
PageJSExecute(inJSStr=f"document.evaluate('{inUIOSelectorStr}', document, null , XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value='{inValue}'")
def UIOSelectorWaitAppear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SEC_FLOAT, inWaitIntervalSecFloat:float = UIO_WAIT_INTERVAL_SEC_FLOAT):
"""L+,W+: Ожидать появление UI элемента на веб странице (блокирует выполнение потока), заданного по UIO селектору inUIOSelectorStr. Выполнять ожидание на протяжении inWaitSecFloat (по умолчанию 60 сек.). Проверка производится с интервалом inWaitIntervalSecFloat (по умолчанию 1 сек.)
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
# UIWeb: Взаимодействие с ui web
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lAppearUIOList = UIWeb.UIOSelectorWaitAppear(inUIOSelectorStr = lUIOSelectorStr)
UIWeb.BrowserClose()
@ -517,14 +626,16 @@ def UIOSelectorWaitAppear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SE
def UIOSelectorWaitDisappear(inUIOSelectorStr:str, inWaitSecFloat:float=UIO_WAIT_SEC_FLOAT, inWaitIntervalSecFloat:float = UIO_WAIT_INTERVAL_SEC_FLOAT):
"""L+,W+: Ожидать исчезновение UI элемента с веб страницы (блокирует выполнение потока), заданного по UIO селектору inUIOSelectorStr. Выполнять ожидание на протяжении inWaitSecFloat (по умолчанию 60 сек.). Проверка производится с интервалом inWaitIntervalSecFloat (по умолчанию 1 сек.)
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
# UIWeb: Взаимодействие с ui web
from pyOpenRPA.Robot import UIWeb
UIWeb.BrowserChromeStart()
UIWeb.PageOpen("https://mail.ru")
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
UIWeb.UIOSelectorWaitDisappear(inUIOSelectorStr = lUIOSelectorStr)
UIWeb.BrowserClose()
@ -556,7 +667,7 @@ def UIOSelectorDetect(inUIOSelectorStr:str) -> str:
# UIWeb: Взаимодействие с ui web
from pyOpenRPA.Robot import UIWeb
lUIOSelectorStr = "#grid > div.grid-middle > div.grid__main-col.svelte-2y66pa > div.grid_newscol.grid_newscol__more-pulse.svelte-1yvqfic > div.grid__ccol.svelte-1yvqfic > ul > li:nth-child(5) > div > a"
lUIOSelectorStr = "//*[@id=\"grid\"]/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lUIOSelectorStr = "//*[@id=\'grid\']/div[2]/div[2]/div[3]/div[1]/ul/li[5]/div/a"
lResultStr = UIWeb.UIOSelectorDetect(inUIOSelectorStr = lUIOSelectorStr)
:param inUIOSelectorStr: XPATH или CSS селектор UI объекта на web странице. Подсказки по CSS: https://devhints.io/css Подсказки по XPath: https://devhints.io/xpath
@ -576,6 +687,8 @@ def UIOSelectorDetect(inUIOSelectorStr:str) -> str:
def UIOMouseSearchInit():
"""L+,W+: Инициализирует процесс поиска UI элемента с помощью мыши. Для прекращения поиска необходимо использовать функцию: UIOMouseSearchReturn
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -603,6 +716,8 @@ def UIOMouseSearchInit():
def UIOMouseSearchReturn():
"""L+,W+: Возвращает UIO объект, над которым находится указатель мыши. Предварительно должна быть вызвана функция UIWeb.UIOMouseSearchInit
!ВНИМАНИЕ! Для работы необходимо проинициализировать webdriver
.. code-block:: python
@ -623,5 +738,7 @@ def UIOMouseSearchReturn():
document.removeEventListener('mousemove', document.ORPASearch);
return document.elementFromPoint(document.ORPAMouseXInt,document.ORPAMouseYInt);
"""
return PageJSExecute(lJSStr)
try:
return PageJSExecute(lJSStr)
except JavascriptException: raise JavascriptException("Отсутствуют координаты для идентификации веб-элемента. Пожалуйста, в следующий раз двигайте мышью")

@ -1,76 +1 @@
import platform
"""
Специфические команды, которые надо выполнять только на ОС семейства Linux: if OS.IS_LINUX_BOOL:
Специфические команды, которые надо выполнять только на ОС семейства Windows: if OS.IS_WINDOWS_BOOL:
"""
IS_LINUX_BOOL = (platform.system().upper()=="LINUX" or platform.system().upper()=="LINUX2")
IS_WINDOWS_BOOL = (platform.system()=="Windows")
def PathStr(inPathStr:str) -> str:
"""Преобразование строк, который содержат путь к каталогу или файлу. В зависимости от операционной системы поддерживаются разные форматы.
Для Windows ОС: path\\to\\file
Для Linux ОС: path/to/file
.. code-block:: python
from pyOpenRPA.Tools import CrossOS
lPathStr = CrossOS.PathStr(inPathStr = 'path/to\\file')
# WINDOWS: lPathStr == 'path\\to\\file'
# LINUX: lPathStr == 'path/to/file'
:param inPathStr: Строка, которую обработать в зависимости от ОС, на которой происходит выполнение
:type inPathStr: str
:return: Обработанная строка с учетом используемой ОС
:rtype: str
"""
if inPathStr is None: return None
if IS_WINDOWS_BOOL:
return inPathStr.replace("/","\\")
if IS_LINUX_BOOL:
return inPathStr.replace("\\","/")
def PathSplitList(inPathStr:str) -> list:
"""Парсинг строки пути на элементы. Учитывает специфику формирования путей в разных ОС (Windows, Linux)
Для Windows ОС: path\\to\\file
Для Linux ОС: path/to/file
.. code-block:: python
from pyOpenRPA.Tools import CrossOS
lPathList = CrossOS.PathSplitList(inPathStr = 'path/to\\file')
# WINDOWS: lPathList == ['path', 'to', 'file']
# LINUX: lPathList == ['path', 'to', 'file']
:param inPathStr: Строка, которую обработать в зависимости от ОС, на которой происходит выполнение
:type inPathStr: str
:return: Массив элементов пути. Пример: ['path', 'to', 'file']
:rtype: list
"""
inPathStr = inPathStr.replace("\\","/")
return inPathStr.split("/")
def PathJoinList(inList: list) ->str:
"""Слияние элементов списка в строку. Учитывает специфику формирования путей в разных ОС (Windows, Linux)
Для Windows ОС: path\\to\\file
Для Linux ОС: path/to/file
.. code-block:: python
from pyOpenRPA.Tools import CrossOS
lPathStr = CrossOS.PathJoinList(inList = ['path', 'to', 'file'] )
# WINDOWS: lPathStr == 'path\\to\\file'
# LINUX: lPathStr == 'path/to/file'
:param inList: Строка, которую обработать в зависимости от ОС, на которой происходит выполнение
:type inList: str
:return: Массив элементов пути. Пример: ['path', 'to', 'file']
:rtype: list
"""
if IS_LINUX_BOOL: return "/".join(inList)
elif IS_WINDOWS_BOOL: return "\\".join(inList)
from ..Utils.CrossOS import *

@ -1,24 +1 @@
import os
import threading
import pdb
import time
"""Module wait file "init_debug" in working directory
"""
gKWARGS = None
def LiveDebugCheckLoop():
while True:
if os.path.exists("init_debug"):
pdb.set_trace()
time.sleep(30.0)
def LiveDebugCheckThread(**inKWARGS):
"""Create thread to wait file appear "init_debug" in the working directory.
"""
global gKWARGS
gKWARGS = inKWARGS
lThread = threading.Thread(target=LiveDebugCheckLoop)
lThread.setName("DEBUG_LIVE")
lThread.start()
from ..Utils.Debugger import *

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save