Общие сведения
Python-скрипт (далее – скрипт) обеспечивает детализацию Drill-through, выполняя ее по условиям, описанным в файле конфигурации config.json (см. Детализация Drill-through). Скрипт необходимо зарегистрировать для приложения при помощи утилиты plm-util, чтобы приложение могло с ним работать.
При регистрации скрипта необходимо указывать его название, которое в приложении отображается в выпадающем списке варианта «Связанный сценарий» контекстного меню факта (см. Детализация Drill-through).
В приложении может быть зарегистрировано сколько угодно много скриптов. Для каждого зарегистрированного скрипта обязательно должен быть предоставлен файл конфигурации config.json (далее – файл конфигурации). Если для какого-либо зарегистрированного скрипта файл конфигурации отсутствует или имеет недопустимую структуру и детализация включена параметром конфигурации приложения plm.user_interface.scenario_hyperlink_enabled, то в ЛЮБОМ воспроизведенном сценарии, для ЛЮБОЙ его мультисферы контекстное меню факта НЕ ОТКРЫВАЕТСЯ, а в лог приложения записывается ошибка вида, соответственно:
Failed to get list of registered python scripts: linked scenario script 'Имя сценария' config file not exists
Failed to get list of registered python scripts: Rapidjson assertion error
Конфигурация зарегистрированного скрипта записывается в директорию /var/plmrepo/linkedscenarioscripts/идентификатор_конфигурации_зарегистрированного_скрипта.
Регистрировать, обновлять и удалять можно только по одному скрипту за раз.
Подготовка к регистрации
Остановить работу приложения:
Убедиться, что в файле конфигурации утилиты plm-util.conf указан параметр, определяющий путь до репозитория Polymatica Analytics, обычно это /var/plmrepo
:
plm.manager.repo_directory = /var/plmrepo
Создать в системе хоста приложения директорию /var/plmrepo/pyscripts/директория_python-скрипта, создать в ней файл с расширением .py и вставить в него код скрипта. Например:
/var/plmrepo/pyscripts/drill_through_on_population_ms/script.py
import argparse
import json
import logging
import os
import sys
from typing import List
def err(err_type: str, msg: str):
json.dump({"type": err_type, "message": msg}, sys.stderr, ensure_ascii=False)
sys.exit(1)
try:
from polymatica import business_scenarios
from polymatica.exceptions import PolymaticaException, RightsError, ScenarioError
except ImportError:
err(err_type="polyapi", msg="polyapi no installed")
sys.stdout = open(1, "w", encoding='utf-8', closefd=False)
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
print("script args: ", sys.argv[1:])
class KeyValue(argparse.Action):
def __call__(self, parser, namespace,
values, option_string=None):
setattr(namespace, self.dest, dict())
for value in values:
key, value = value.split('=')
getattr(namespace, self.dest)[key] = value
class LoggerDisabler:
"""
Контекстный менеджер, временно отключающий логгирование,
чтобы ошибки из полиапи не попадали напрямую в stderr, так как они
передаются в этот скрипт и через функцию err попадают в stderr внутри json.
"""
def __enter__(self):
logging.disable(logging.ERROR)
def __exit__(self, exc_type, exc_val, exc_tb):
logging.disable(logging.NOTSET)
parser = argparse.ArgumentParser()
parser.add_argument('--dimension-elements',
nargs='*',
action=KeyValue)
parser.add_argument('--connection_session', nargs=3)
parser.add_argument('--url')
parser.add_argument('--layer_id')
parser.add_argument('--scenario_id')
parser.add_argument('--cube_id')
parser.add_argument('--config_path', default=os.path.join(ROOT_PATH, 'config.json'))
args = parser.parse_args()
print("dim args: ", args.dimension_elements)
class DimFilter:
def __init__(self, dim_id: str, filter_val: str):
self.dim_id = dim_id
self.filter_val = filter_val
class MapInfo:
def __init__(self, scenario_id: str, cube_id: str, filters: List[DimFilter]):
self.scenario_id = scenario_id # 1. запустить
self.cube_id = cube_id # 2. найти на слое мультисферы этого куба
self.filters = filters # 3. применить к мультисферам
def extract_map_info(cfg) -> MapInfo:
for scenario_pair in cfg['scenarios']:
from_sc_id = scenario_pair['from_scenario_id']
to_sc_id = scenario_pair['to_scenario_id']
if args.scenario_id == from_sc_id:
dims_to_filter = []
to_cube_id = 0
for cube_pair in scenario_pair['cubes']:
if cube_pair['from_cube_id'] == args.cube_id:
to_cube_id = cube_pair['to_cube_id']
for dim in cube_pair['dimensions']:
from_dim_id = dim['from_dimension_id']
to_dim_id = dim['to_dimension_id']
if from_dim_id in args.dimension_elements.keys():
dim_filter = DimFilter(dim_id=to_dim_id,
filter_val=args.dimension_elements[from_dim_id].encode('utf8',
'surrogateescape').decode(
'utf8'))
dims_to_filter.append(dim_filter)
break
return MapInfo(filters=dims_to_filter, cube_id=to_cube_id, scenario_id=to_sc_id)
raise Exception("в конфиге не найдена пара сценариев")
try:
with open(args.config_path) as f:
json_config = json.load(f)
except OSError:
err(err_type="os", msg="cannot open {}".format(args.config_path))
except json.JSONDecodeError as e:
err(err_type="json", msg=str(e))
try:
map_info = extract_map_info(json_config)
except Exception as e:
err(err_type="config", msg="Неверный формат конфига: {}".format(str(e)))
if len(map_info.filters) != len(args.dimension_elements):
err(err_type="config",
msg="Пары для размерностей \"{}\" не заданы в конфиге".format(args.dimension_elements.keys()))
try:
session_params = {"session_id": args.connection_session[0],
"manager_uuid": args.connection_session[1],
"full_polymatica_version": args.connection_session[2]}
with LoggerDisabler():
bs = business_scenarios.BusinessLogic("", session_auth=session_params, url=json_config['connection']['url'])
except Exception as e:
err(err_type="connect", msg=str(e))
print("starting scenario \"{}\" on layer \"{}\"".format(map_info.scenario_id, args.layer_id))
try:
with LoggerDisabler():
bs._check_scenario_cubes_permission(scenario_id=map_info.scenario_id)
if bs.check_scenarios_dims_facts_persmission(scenario_id=map_info.scenario_id):
bs.run_scenario_on_layer(scenario_id=map_info.scenario_id, layer_id=args.layer_id)
except RightsError as e:
err(err_type="rights", msg="Не удалось запустить связанный сценарий: " + e.user_msg)
except ScenarioError as e:
err(err_type="scenario", msg="Не удалось запустить связанный сценарий: " + e.user_msg)
except Exception as e:
err(err_type="scenario", msg="Не удалось запустить связанный сценарий")
print("scenario \"{}\" successfully finished on layer \"{}\"".format(map_info.scenario_id, args.layer_id))
modules_to_filter = []
for module_info in bs.get_module_list():
if module_info[3] == map_info.cube_id:
modules_to_filter.append(module_info[0])
if len(modules_to_filter) == 0:
err(err_type="module",
msg="Не найден модуль созданный из куба с id \"{}\" в сценарии \"{}\"".format(map_info.cube_id,
map_info.scenario_id))
def find_dim(dims_list, dim_id):
for dim in dims_list:
if dim.get('id', '') == dim_id:
return dim
return None
for module_id in modules_to_filter:
bs.set_multisphere_module_id(module_id)
dims = bs._get_dimensions_list()
for filter_to_put in map_info.filters:
dim = find_dim(dims, filter_to_put.dim_id)
if not dim:
continue
try:
bs.put_dim_filter_by_value(dim_id=filter_to_put.dim_id, value=filter_to_put.filter_val,
clear_filter=True)
print(
"putted filter with value - \"{}\" on dim with id - \"{}\"".format(filter_to_put.filter_val,
filter_to_put.dim_id))
except PolymaticaException as e: # если фильтр исключен другим фильтром то игнорим его
print(
"skipped filter with value - \"{}\" on dim with id - \"{}\"".format(filter_to_put.filter_val,
filter_to_put.dim_id))
Регистрация скрипта через конфигурационный файл plm-util
Регистрация скрипта
Открыть на редактирование файл plm-util.config:
nano /etc/polymatica/plm-util.conf
Добавить в него строки:
# Команда регистрации скрипта
create-pyscript
# Название регистрируемого скрипта Python
pyscripts.create.name = Python Linked Scenario
# Вид регистрируемого скрипта Python [formatted_export, linked_scenario]
pyscripts.create.type = linked_scenario
# Путь до регистрируемого скрипта Python на диске
pyscripts.create.script = /var/plmscripts/linked_scenario/first/script.py
# Идентификатор размерности, в которой начинается переход в связанный сценарий для вида "linked_scenario"
pyscripts.create.measure_id = 07141982
Сохранить изменения в plm-util.config и выполнить команду:
plm-util --create-pyscript
Обновление скрипта
Открыть на редактирование файл plm-util.config:
nano /etc/polymatica/plm-util.conf
Добавить (раскомментировать) строки:
# Команда обновления скрипта
update-pyscript
# Внесение изменений в зарегистрированный скрипт Python по имени
# pyscripts.update.by_name = Python Linked Scenario
# либо
# Внесение изменений в зарегистрированный скрипт Python по идентификатору
# pyscripts.update.by_id = 1a71ec1d
# Новое наименование для скрипта Python
pyscripts.update.new_name = New PlmUtil Script
# Новый тип скрипта Python
pyscripts.update.new_type = formatted_export
# Путь до нового скрипта Python.
pyscripts.update.new_script = /var/plmscripts/formatted_export/test_python_script_2.py
Сохранить изменения в plm-util.config и выполнить команду:
plm-util --update-pyscript
Удаление скрипта
Открыть на редактирование файл plm-util.config:
nano /etc/polymatica/plm-util.conf
Добавить в него строки:
delete-pyscript
# Удаление зарегистрированного скрипта Python по имени
# pyscripts.delete.by_name = Python Linked Scenario
либо
# Удаление зарегистрированного скрипта Python по идентификатору
# pyscripts.delete.by_id = e654a03f
Выполните команду:
plm-util --delete-pyscript
После успешного завершения процедуры можно запустить сервисы Polymatica командой:
systemctl start polymatica.service