polyapi 5.9.13__tar.gz → 5.9.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {polyapi-5.9.13 → polyapi-5.9.14}/PKG-INFO +1 -1
- {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/PKG-INFO +1 -1
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/business_scenarios.py +143 -76
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/consts.py +1 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/error_handler.py +32 -33
- {polyapi-5.9.13 → polyapi-5.9.14}/pyproject.toml +1 -1
- {polyapi-5.9.13 → polyapi-5.9.14}/setup.py +1 -1
- {polyapi-5.9.13 → polyapi-5.9.14}/LICENSE.txt +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/README.txt +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/SOURCES.txt +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/dependency_links.txt +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/requires.txt +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/top_level.txt +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/__init__.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/authorization.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/business_logic_doc.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/__init__.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/base_command.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/olap_module.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/other_modules.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/__init__.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/helper_funcs.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/params_models.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/exceptions.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/executor.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/__init__.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/base_graph.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/graph_interface.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/__init__.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/areas.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/balls.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/chord.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/circles.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/circles_series.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/corridors.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/cumulative_areas.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/cumulative_cylinders.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/cylinders.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/graph.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/lines.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/pies.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/point.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/point_series.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/pools.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/pools_3d.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/radar.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/sankey.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/surface.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/helper.py +0 -0
- {polyapi-5.9.13 → polyapi-5.9.14}/setup.cfg +0 -0
|
@@ -41,6 +41,7 @@ from polymatica.common import (
|
|
|
41
41
|
POSITION_MAP,
|
|
42
42
|
ROOT_PARENT,
|
|
43
43
|
TYPES_MAP,
|
|
44
|
+
UNITS_LOAD_DATA_CHUNK,
|
|
44
45
|
UPDATE_PERIOD,
|
|
45
46
|
TypeConverter,
|
|
46
47
|
json_type,
|
|
@@ -90,8 +91,6 @@ from polymatica.helper import Helper
|
|
|
90
91
|
logger = logging.getLogger(__name__)
|
|
91
92
|
logger.addHandler(NullHandler())
|
|
92
93
|
|
|
93
|
-
UNITS_LOAD_DATA_CHUNK = 1000
|
|
94
|
-
|
|
95
94
|
|
|
96
95
|
# ----------------------------------------------------------------------------------------------------------------------
|
|
97
96
|
|
|
@@ -208,6 +207,7 @@ class BusinessLogic:
|
|
|
208
207
|
jupiter: bool = False,
|
|
209
208
|
language: str = "ru",
|
|
210
209
|
suffixes_url: List = None,
|
|
210
|
+
script_mode: bool = False
|
|
211
211
|
):
|
|
212
212
|
"""
|
|
213
213
|
Инициализация класса BusinessLogic.
|
|
@@ -225,6 +225,8 @@ class BusinessLogic:
|
|
|
225
225
|
:param language: (необязательный) язык локализации; возможны значения: "ru"/"en"/"de"/"fr"; по-умолчанию "ru".
|
|
226
226
|
:param suffixes_url: (необязательный) список возможных суффиксов URL-адреса стенда Полиматики;
|
|
227
227
|
рекомендуется не задавать этот параметр без острой на то необходимости.
|
|
228
|
+
:param script_mode: (необязательный) используется ли класс BusinessLogic для специального скрипта,
|
|
229
|
+
по умолчанию False, внутренний параметр.
|
|
228
230
|
"""
|
|
229
231
|
logger.info("BusinessLogic init")
|
|
230
232
|
|
|
@@ -357,6 +359,7 @@ class BusinessLogic:
|
|
|
357
359
|
|
|
358
360
|
self.API_VERSION = API_VERSION
|
|
359
361
|
self.ROOT_PARENT = ROOT_PARENT
|
|
362
|
+
self.script_mode = script_mode
|
|
360
363
|
|
|
361
364
|
def checks(self, func_name: str, *args):
|
|
362
365
|
"""
|
|
@@ -841,13 +844,6 @@ class BusinessLogic:
|
|
|
841
844
|
:param num_col: количество отображаемых столбцов
|
|
842
845
|
:return: (dict) multisphere data, format: {"dimensions": "", "facts": "", "data": ""}
|
|
843
846
|
"""
|
|
844
|
-
# список слоев
|
|
845
|
-
layers_list = self._get_session_layers()
|
|
846
|
-
try:
|
|
847
|
-
# получить layer id
|
|
848
|
-
self.layer_id = layers_list[0]["uuid"]
|
|
849
|
-
except (KeyError, IndexError) as e:
|
|
850
|
-
return self._raise_exception(PolymaticaException, str(e))
|
|
851
847
|
|
|
852
848
|
# инициализация модуля Olap
|
|
853
849
|
self.set_multisphere_module_id(self.multisphere_module_id)
|
|
@@ -1610,7 +1606,7 @@ class BusinessLogic:
|
|
|
1610
1606
|
# если же в мультисфере нет указанной группы фактов - выбрасываем исключение
|
|
1611
1607
|
if not query:
|
|
1612
1608
|
return self._raise_exception(
|
|
1613
|
-
ValueError, f
|
|
1609
|
+
ValueError, f'Group "{group}" not found', with_traceback=False
|
|
1614
1610
|
)
|
|
1615
1611
|
|
|
1616
1612
|
# снять выделение фактов
|
|
@@ -1659,7 +1655,7 @@ class BusinessLogic:
|
|
|
1659
1655
|
# если же в мультисфере нет указанной группы фактов - выбрасываем исключение
|
|
1660
1656
|
if not query:
|
|
1661
1657
|
return self._raise_exception(
|
|
1662
|
-
ValueError, f
|
|
1658
|
+
ValueError, f'Group "{group}" not found', with_traceback=False
|
|
1663
1659
|
)
|
|
1664
1660
|
|
|
1665
1661
|
# снять выделение фактов
|
|
@@ -1675,9 +1671,6 @@ class BusinessLogic:
|
|
|
1675
1671
|
return self.h.parse_result(result, "facts") or list()
|
|
1676
1672
|
|
|
1677
1673
|
def _get_tree_dimension_list(self) -> List:
|
|
1678
|
-
result = self.execute_olap_command(
|
|
1679
|
-
command_name="fact", state="tree_get_request"
|
|
1680
|
-
)
|
|
1681
1674
|
result = self.execute_olap_command(
|
|
1682
1675
|
command_name="dimension", state="tree_get_request"
|
|
1683
1676
|
)
|
|
@@ -1894,7 +1887,8 @@ class BusinessLogic:
|
|
|
1894
1887
|
), f'File "{download_file_name}" not in path "{path}"!'
|
|
1895
1888
|
return download_file_name, path
|
|
1896
1889
|
|
|
1897
|
-
|
|
1890
|
+
@staticmethod
|
|
1891
|
+
def _is_numeric(value: str) -> bool:
|
|
1898
1892
|
"""
|
|
1899
1893
|
Проверка, является ли заданная строка числом.
|
|
1900
1894
|
:param value: (str) строка для проверки.
|
|
@@ -2013,7 +2007,7 @@ class BusinessLogic:
|
|
|
2013
2007
|
)
|
|
2014
2008
|
top_dims = self.h.parse_result(result=result, key="top_dims")
|
|
2015
2009
|
|
|
2016
|
-
# сформировать словарь {"элемент верхней размерности": индекс_
|
|
2010
|
+
# сформировать словарь {"элемент верхней размерности": индекс_элемента}
|
|
2017
2011
|
filter_pattern_result = self.filter_pattern_change(top_dims[0], 30, [])
|
|
2018
2012
|
top_dim_values = self.h.parse_result(
|
|
2019
2013
|
result=filter_pattern_result, key="data"
|
|
@@ -2654,7 +2648,7 @@ class BusinessLogic:
|
|
|
2654
2648
|
break
|
|
2655
2649
|
return {measure.get("fact_id") for measure in measure_data}
|
|
2656
2650
|
|
|
2657
|
-
def _prepare_data(self) ->
|
|
2651
|
+
def _prepare_data(self) -> Tuple[List[Any], int, int]:
|
|
2658
2652
|
"""
|
|
2659
2653
|
Подготовка данных для дальнейшего получения датафрейма:
|
|
2660
2654
|
1. Формирование колонок мультисферы с учётом вынесенных верхних размерностей.
|
|
@@ -3515,7 +3509,9 @@ class BusinessLogic:
|
|
|
3515
3509
|
return ids
|
|
3516
3510
|
|
|
3517
3511
|
@timing
|
|
3518
|
-
def sort_measure(
|
|
3512
|
+
def sort_measure(
|
|
3513
|
+
self, measure_name: str, sort_type: str, path: List[str] = None
|
|
3514
|
+
) -> dict:
|
|
3519
3515
|
"""
|
|
3520
3516
|
Сортировка значений указанного факта по возрастанию/убыванию.
|
|
3521
3517
|
Необходимым условием сортировки является наличие хотя бы одной вынесенной влево размерности.
|
|
@@ -3540,10 +3536,9 @@ class BusinessLogic:
|
|
|
3540
3536
|
module_configuration = self.execute_olap_command(
|
|
3541
3537
|
command_name="view", state="get_module_configuration"
|
|
3542
3538
|
)
|
|
3543
|
-
top_total_transfer =
|
|
3544
|
-
"top_total_transfer"
|
|
3545
|
-
|
|
3546
|
-
|
|
3539
|
+
top_total_transfer = self.h.parse_result(
|
|
3540
|
+
module_configuration, "top_total_transfer"
|
|
3541
|
+
)
|
|
3547
3542
|
# получаем данные по фактам, а также количество левых размерностей
|
|
3548
3543
|
result = self.execute_olap_command(
|
|
3549
3544
|
command_name="view", state="get", from_row=0, from_col=0, num_row=1
|
|
@@ -3581,7 +3576,7 @@ class BusinessLogic:
|
|
|
3581
3576
|
|
|
3582
3577
|
if path and line is None:
|
|
3583
3578
|
return self._raise_exception(
|
|
3584
|
-
ValueError, f
|
|
3579
|
+
ValueError, f"Path {path} is invalid! All top dims must be expanded!"
|
|
3585
3580
|
)
|
|
3586
3581
|
|
|
3587
3582
|
self.func_name = "sort_measure"
|
|
@@ -3590,8 +3585,9 @@ class BusinessLogic:
|
|
|
3590
3585
|
)
|
|
3591
3586
|
|
|
3592
3587
|
@staticmethod
|
|
3593
|
-
def _find_column_index(
|
|
3594
|
-
|
|
3588
|
+
def _find_column_index(
|
|
3589
|
+
top: Dict, path: List[str], measure_id: str
|
|
3590
|
+
) -> Optional[int]:
|
|
3595
3591
|
acceptable_indieces = set(range(len(top[0])))
|
|
3596
3592
|
value_index = len(top[0]) + 1
|
|
3597
3593
|
for depth, expected_value in enumerate(path):
|
|
@@ -3599,9 +3595,13 @@ class BusinessLogic:
|
|
|
3599
3595
|
acceptable_indieces_row = set()
|
|
3600
3596
|
|
|
3601
3597
|
for index_cell, cell in enumerate(row):
|
|
3602
|
-
if
|
|
3598
|
+
if (
|
|
3599
|
+
expected_value == "Всего"
|
|
3600
|
+
and cell.get("type") == 5
|
|
3601
|
+
and index_cell in acceptable_indieces
|
|
3602
|
+
):
|
|
3603
3603
|
acceptable_indieces_row.add(index_cell)
|
|
3604
|
-
|
|
3604
|
+
value_index = index_cell
|
|
3605
3605
|
elif cell.get("value") == expected_value:
|
|
3606
3606
|
if index_cell in acceptable_indieces:
|
|
3607
3607
|
value_index = index_cell
|
|
@@ -3609,7 +3609,11 @@ class BusinessLogic:
|
|
|
3609
3609
|
elif cell.get("type") in (1, 5) and index_cell >= value_index:
|
|
3610
3610
|
if index_cell in acceptable_indieces:
|
|
3611
3611
|
acceptable_indieces_row.add(index_cell)
|
|
3612
|
-
elif
|
|
3612
|
+
elif (
|
|
3613
|
+
cell.get("value") != expected_value
|
|
3614
|
+
and cell.get("type") in (2, 3)
|
|
3615
|
+
and acceptable_indieces_row
|
|
3616
|
+
):
|
|
3613
3617
|
break
|
|
3614
3618
|
acceptable_indieces = acceptable_indieces_row
|
|
3615
3619
|
value_index = len(top[0]) + 1
|
|
@@ -3617,7 +3621,10 @@ class BusinessLogic:
|
|
|
3617
3621
|
# находим факт в ряду фактов и возвращаем его индекс
|
|
3618
3622
|
for index in sorted(acceptable_indieces):
|
|
3619
3623
|
measure_cell = top[-1][index]
|
|
3620
|
-
if
|
|
3624
|
+
if (
|
|
3625
|
+
measure_cell.get("type") == 4
|
|
3626
|
+
and measure_cell.get("fact_id") == measure_id
|
|
3627
|
+
):
|
|
3621
3628
|
return index
|
|
3622
3629
|
return None
|
|
3623
3630
|
|
|
@@ -3962,9 +3969,9 @@ class BusinessLogic:
|
|
|
3962
3969
|
user_schedule["time_zone"] = time_zones[h_timezone]
|
|
3963
3970
|
|
|
3964
3971
|
# преобразование времени в UNIX time
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
user_schedule["time"] = int(
|
|
3972
|
+
hours, minutes = user_schedule["time"].split(":")
|
|
3973
|
+
t = datetime.timedelta(hours=int(hours), minutes=int(minutes))
|
|
3974
|
+
user_schedule["time"] = int(t.total_seconds())
|
|
3968
3975
|
|
|
3969
3976
|
def _get_interval_borders(
|
|
3970
3977
|
self, user_interval: str, interval_borders: list
|
|
@@ -4020,10 +4027,12 @@ class BusinessLogic:
|
|
|
4020
4027
|
'schedule': <dict>
|
|
4021
4028
|
}, где:
|
|
4022
4029
|
type - тип обновления, возможны значения:
|
|
4023
|
-
["ручное"
|
|
4030
|
+
["полное" (ранее было "ручное" и "по расписанию" (при наличии расписания в schedule),
|
|
4031
|
+
эти значения тоже доступны для обратной совместимости),
|
|
4032
|
+
"интервальное", "инкрементальное", "обновление измененных записей"];
|
|
4024
4033
|
|
|
4025
|
-
schedule - планировщик, позволяющий задать
|
|
4026
|
-
|
|
4034
|
+
schedule - планировщик, позволяющий задать расписание для обновления. Если расписание
|
|
4035
|
+
задавать не надо, то необходимо передать 'schedule': {}.
|
|
4027
4036
|
Имеет структуру:
|
|
4028
4037
|
{
|
|
4029
4038
|
'type': <value>,
|
|
@@ -4043,14 +4052,14 @@ class BusinessLogic:
|
|
|
4043
4052
|
актуально только для периода обновления "Ежемесячно".
|
|
4044
4053
|
Также может быть списком, содержащим словари указанной выше структуры.
|
|
4045
4054
|
Пример:
|
|
4046
|
-
|
|
4047
|
-
"type": "
|
|
4055
|
+
{
|
|
4056
|
+
"type": "полное",
|
|
4048
4057
|
"schedule": {
|
|
4049
4058
|
"type": "Ежедневно",
|
|
4050
4059
|
"time": "18:30",
|
|
4051
4060
|
"time_zone": "UTC+3:00"
|
|
4052
4061
|
}
|
|
4053
|
-
}
|
|
4062
|
+
}
|
|
4054
4063
|
:param sql_params: (dict) параметры для источника данных SQL.
|
|
4055
4064
|
Поля, передаваемые в словарь:
|
|
4056
4065
|
"server" - хост, который может быть задан в виде IP-адреса сервера (например, "10.18.0.132"),
|
|
@@ -4067,7 +4076,7 @@ class BusinessLogic:
|
|
|
4067
4076
|
"server": "10.8.0.115:5433",
|
|
4068
4077
|
"login": "your_user",
|
|
4069
4078
|
"passwd": "your_password",
|
|
4070
|
-
"database" "database_name",
|
|
4079
|
+
"database": "database_name",
|
|
4071
4080
|
"sql_query": "SELECT * FROM table"
|
|
4072
4081
|
}
|
|
4073
4082
|
:param user_interval: (str) интервал обновлений; возможны значения:
|
|
@@ -4118,6 +4127,16 @@ class BusinessLogic:
|
|
|
4118
4127
|
if update_params is None
|
|
4119
4128
|
else copy.deepcopy(update_params)
|
|
4120
4129
|
)
|
|
4130
|
+
if update_params is not None and update_params.get("type") in ("ручное", "по расписанию"):
|
|
4131
|
+
logger.warning('Params "ручное" and "по расписанию" from update_params["type"] will be deprecated '
|
|
4132
|
+
'in future version. Use "полное" with or without schedule instead.')
|
|
4133
|
+
# отправляем на сервер старые названия типов обновления
|
|
4134
|
+
if current_update_params.get("type") == "полное":
|
|
4135
|
+
if not current_update_params.get("schedule"):
|
|
4136
|
+
current_update_params["type"] = "ручное"
|
|
4137
|
+
else:
|
|
4138
|
+
current_update_params["type"] = "по расписанию"
|
|
4139
|
+
|
|
4121
4140
|
time_zones = self.server_codes["manager"]["timezone"]
|
|
4122
4141
|
if modified_records_params is None:
|
|
4123
4142
|
modified_records_params = dict()
|
|
@@ -4190,7 +4209,7 @@ class BusinessLogic:
|
|
|
4190
4209
|
increment_dim,
|
|
4191
4210
|
interval_dim,
|
|
4192
4211
|
interval_borders,
|
|
4193
|
-
encoding
|
|
4212
|
+
encoding,
|
|
4194
4213
|
)
|
|
4195
4214
|
except Exception as e:
|
|
4196
4215
|
return self._raise_exception(ValueError, str(e), with_traceback=False)
|
|
@@ -4461,10 +4480,12 @@ class BusinessLogic:
|
|
|
4461
4480
|
'schedule': <dict>
|
|
4462
4481
|
}, где:
|
|
4463
4482
|
type - тип обновления, возможны значения:
|
|
4464
|
-
|
|
4483
|
+
["полное" (ранее было "ручное" и "по расписанию" (при наличии расписания в schedule),
|
|
4484
|
+
эти значения тоже доступны для обратной совместимости),
|
|
4485
|
+
"интервальное", "инкрементальное", "обновление измененных записей"];
|
|
4465
4486
|
|
|
4466
|
-
schedule - планировщик, позволяющий задать
|
|
4467
|
-
|
|
4487
|
+
schedule - планировщик, позволяющий задать расписание для обновления. Если расписание
|
|
4488
|
+
задавать не надо, то необходимо передать 'schedule': {}.
|
|
4468
4489
|
Имеет структуру:
|
|
4469
4490
|
{
|
|
4470
4491
|
'type': <value>,
|
|
@@ -4485,7 +4506,7 @@ class BusinessLogic:
|
|
|
4485
4506
|
Также может быть списком, содержащим словари указанной выше структуры.
|
|
4486
4507
|
Пример:
|
|
4487
4508
|
[{
|
|
4488
|
-
"type": "
|
|
4509
|
+
"type": "полное",
|
|
4489
4510
|
"schedule": {
|
|
4490
4511
|
"type": "Ежедневно",
|
|
4491
4512
|
"time": "18:30",
|
|
@@ -4531,6 +4552,16 @@ class BusinessLogic:
|
|
|
4531
4552
|
if update_params is None
|
|
4532
4553
|
else copy.deepcopy(update_params)
|
|
4533
4554
|
)
|
|
4555
|
+
if update_params is not None and update_params.get("type") in ("ручное", "по расписанию"):
|
|
4556
|
+
logger.warning('Params "ручное" and "по расписанию" from update_params["type"] will be deprecated '
|
|
4557
|
+
'in future version. Use "полное" with or without schedule instead.')
|
|
4558
|
+
# отправляем на сервер старые названия типов обновления
|
|
4559
|
+
if current_update_params.get("type") == "полное":
|
|
4560
|
+
if not current_update_params.get("schedule"):
|
|
4561
|
+
current_update_params["type"] = "ручное"
|
|
4562
|
+
else:
|
|
4563
|
+
current_update_params["type"] = "по расписанию"
|
|
4564
|
+
|
|
4534
4565
|
time_zones = self.server_codes["manager"]["timezone"]
|
|
4535
4566
|
if modified_records_params is None:
|
|
4536
4567
|
modified_records_params = dict()
|
|
@@ -5974,10 +6005,14 @@ class BusinessLogic:
|
|
|
5974
6005
|
session_layers_lst = self._get_session_layers()
|
|
5975
6006
|
self.layers_list = [layer.get("uuid") for layer in session_layers_lst]
|
|
5976
6007
|
|
|
5977
|
-
# получить идентификатор текущего слоя, если он не
|
|
6008
|
+
# получить идентификатор текущего слоя, если он не задан, или создать новый слой
|
|
5978
6009
|
if not self.active_layer_id:
|
|
5979
6010
|
try:
|
|
5980
|
-
|
|
6011
|
+
if session_layers_lst:
|
|
6012
|
+
self.active_layer_id = session_layers_lst[0].get("uuid")
|
|
6013
|
+
else:
|
|
6014
|
+
self.active_layer_id = self.create_layer()
|
|
6015
|
+
self.layers_list.append(self.active_layer_id)
|
|
5981
6016
|
except Exception as e:
|
|
5982
6017
|
error_msg = f"Error while parsing response: {e}"
|
|
5983
6018
|
return self._raise_exception(PolymaticaException, error_msg)
|
|
@@ -6385,11 +6420,12 @@ class BusinessLogic:
|
|
|
6385
6420
|
Возвращает название активного слоя.
|
|
6386
6421
|
:return: (str) название слоя.
|
|
6387
6422
|
"""
|
|
6388
|
-
|
|
6423
|
+
result = self.execute_manager_command(
|
|
6389
6424
|
command_name="user_layer",
|
|
6390
6425
|
state="get_layer",
|
|
6391
6426
|
layer_id=self.get_active_layer_id(),
|
|
6392
|
-
)
|
|
6427
|
+
)
|
|
6428
|
+
return self.h.parse_result(result, "layer", "name")
|
|
6393
6429
|
|
|
6394
6430
|
def _get_modules_in_layer(self, layer_id: str, is_int_type: bool = True) -> List:
|
|
6395
6431
|
"""
|
|
@@ -6419,7 +6455,14 @@ class BusinessLogic:
|
|
|
6419
6455
|
)
|
|
6420
6456
|
cube_id = module.get("cube_id")
|
|
6421
6457
|
|
|
6422
|
-
|
|
6458
|
+
if self.script_mode:
|
|
6459
|
+
module_setting = self.execute_manager_command(
|
|
6460
|
+
command_name="user_iface", state="load_settings", module_id=module_id)
|
|
6461
|
+
module_info = self.h.parse_result(result=module_setting, key="settings") or dict()
|
|
6462
|
+
facts_format = module_info.get('config_storage')['facts-format']['__suffixes']
|
|
6463
|
+
result.append([module_id, module_name, module_type, facts_format])
|
|
6464
|
+
else:
|
|
6465
|
+
result.append([module_id, module_name, module_type, cube_id])
|
|
6423
6466
|
return result
|
|
6424
6467
|
|
|
6425
6468
|
@timing
|
|
@@ -7081,7 +7124,7 @@ class BusinessLogic:
|
|
|
7081
7124
|
self, module: str = "", width: int = 200, olap_resize: bool = False
|
|
7082
7125
|
) -> dict:
|
|
7083
7126
|
"""
|
|
7084
|
-
|
|
7127
|
+
Изменение ширины колонок фактов (чтобы текст на них становился видимым).
|
|
7085
7128
|
Некая имитация интерфейсной кнопки "Показать контент". Актуально только для OLAP-модулей (мультисфер).
|
|
7086
7129
|
Если пользователем не указан идентификатор модуля, то расширяется текущий активный OLAP-модуль.
|
|
7087
7130
|
:param module: название/идентификатор OLAP-модуля;
|
|
@@ -7089,7 +7132,8 @@ class BusinessLogic:
|
|
|
7089
7132
|
если модуль не указан, то берётся текущий (активный) модуль (если его нет - сгенерируется исключение).
|
|
7090
7133
|
:param width: ширина, на которую будет меняться каждая колонка фактов; можно указать отрицательное значение,
|
|
7091
7134
|
тогда ширина колонок будет уменьшаться; при указании положительного значения - ширина колонок увеличится.
|
|
7092
|
-
:param olap_resize: нужно ли
|
|
7135
|
+
:param olap_resize: нужно ли изменять ширину окна мультисферы
|
|
7136
|
+
(True - нужно, False - не нужно). По-умолчанию False.
|
|
7093
7137
|
:return: результат команды ("user_iface", "save_settings").
|
|
7094
7138
|
:call_example:
|
|
7095
7139
|
1. Инициализируем класс: bl_test = sc.BusinessLogic(login="login", password="password", url="url")
|
|
@@ -7624,7 +7668,7 @@ class BusinessLogic:
|
|
|
7624
7668
|
Получить тип обновления мультисферы с заданным идентификатором.
|
|
7625
7669
|
:param cube_id: (str) идентификатор куба.
|
|
7626
7670
|
:return: (str) тип обновления; возможен один из вариантов:
|
|
7627
|
-
["
|
|
7671
|
+
["полное", "интервальное", "инкрементальное", "обновление измененных записей", "undefined"];
|
|
7628
7672
|
первые четыре типа актуальны для БД-источников, последний тип - для файловых источников.
|
|
7629
7673
|
"""
|
|
7630
7674
|
empty_id = "00000000"
|
|
@@ -7656,12 +7700,14 @@ class BusinessLogic:
|
|
|
7656
7700
|
)
|
|
7657
7701
|
if interval and interval.get("dimension_id", empty_id) != empty_id:
|
|
7658
7702
|
return "интервальное"
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7703
|
+
if (
|
|
7704
|
+
self.h.parse_result(
|
|
7705
|
+
metadata_result, "delta", default_value=dict()
|
|
7706
|
+
).get("primary_key_dim", empty_id)
|
|
7707
|
+
!= empty_id
|
|
7708
|
+
):
|
|
7709
|
+
return "обновление измененных записей"
|
|
7710
|
+
return "полное"
|
|
7665
7711
|
|
|
7666
7712
|
@timing
|
|
7667
7713
|
def get_cube_info(self, cube: str = "") -> dict:
|
|
@@ -7706,6 +7752,17 @@ class BusinessLogic:
|
|
|
7706
7752
|
cube_info = dict()
|
|
7707
7753
|
for cube in cubes:
|
|
7708
7754
|
cube_name, cube_id = cube.get("name"), cube.get("uuid")
|
|
7755
|
+
if self.script_mode:
|
|
7756
|
+
result = self.execute_olap_command(command_name="view",
|
|
7757
|
+
state="get",
|
|
7758
|
+
from_row=0,
|
|
7759
|
+
from_col=0,
|
|
7760
|
+
num_row=1,
|
|
7761
|
+
num_col=1)
|
|
7762
|
+
relevance_date = self.h.parse_result(result, "relevance_date", default_value="")
|
|
7763
|
+
else:
|
|
7764
|
+
relevance_date = ""
|
|
7765
|
+
|
|
7709
7766
|
if current_cube not in (cube_name, cube_id):
|
|
7710
7767
|
continue
|
|
7711
7768
|
cube_info.update(
|
|
@@ -7727,6 +7784,7 @@ class BusinessLogic:
|
|
|
7727
7784
|
"dimension_count": cube.get("dimension_count"),
|
|
7728
7785
|
"measure_count": cube.get("fact_count"),
|
|
7729
7786
|
"update_type": self._get_cube_update_type(cube_id),
|
|
7787
|
+
"relevance_date": relevance_date
|
|
7730
7788
|
}
|
|
7731
7789
|
)
|
|
7732
7790
|
break
|
|
@@ -8415,13 +8473,17 @@ class GetDataChunk:
|
|
|
8415
8473
|
# получаем левые/верхние размерности, считаем их количество
|
|
8416
8474
|
self.left_dims, self.top_dims = self._get_active_dims()
|
|
8417
8475
|
|
|
8476
|
+
# внутренний флаг работы со скриптом
|
|
8477
|
+
self.script_mode = sc.script_mode
|
|
8478
|
+
|
|
8418
8479
|
# обязательное условие: чтобы получить данные, должна быть вынесена хотя бы одна левая размерность
|
|
8419
|
-
|
|
8480
|
+
# (кроме специальных скриптов)
|
|
8481
|
+
if not self.left_dims and not self.script_mode:
|
|
8420
8482
|
error_msg = (
|
|
8421
8483
|
"For page-by-page loading of the multisphere, "
|
|
8422
8484
|
"at least one dimension must be moved to the left!"
|
|
8423
8485
|
)
|
|
8424
|
-
|
|
8486
|
+
self._raise_exception(
|
|
8425
8487
|
PolymaticaException, error_msg, with_traceback=False
|
|
8426
8488
|
)
|
|
8427
8489
|
|
|
@@ -8666,7 +8728,7 @@ class GetDataChunk:
|
|
|
8666
8728
|
show_horz_total=show_full,
|
|
8667
8729
|
show_vert_total=show_full,
|
|
8668
8730
|
)
|
|
8669
|
-
return response
|
|
8731
|
+
return self.h.parse_result(response, "total_row")
|
|
8670
8732
|
|
|
8671
8733
|
def load_sphere_chunk(
|
|
8672
8734
|
self,
|
|
@@ -8821,7 +8883,7 @@ class GetDataChunk:
|
|
|
8821
8883
|
state="list_rq",
|
|
8822
8884
|
)
|
|
8823
8885
|
dimensions: Dict[dict] = {
|
|
8824
|
-
dim["id"]: dim for dim in response
|
|
8886
|
+
dim["id"]: dim for dim in self.h.parse_result(response, "dimensions")
|
|
8825
8887
|
}
|
|
8826
8888
|
|
|
8827
8889
|
# get facts
|
|
@@ -8830,7 +8892,7 @@ class GetDataChunk:
|
|
|
8830
8892
|
state="list_rq",
|
|
8831
8893
|
)
|
|
8832
8894
|
facts: Dict[dict] = {
|
|
8833
|
-
dim["id"]: dim for dim in response
|
|
8895
|
+
dim["id"]: dim for dim in self.h.parse_result(response, "facts")
|
|
8834
8896
|
}
|
|
8835
8897
|
|
|
8836
8898
|
# load 1 row with 1 col to get valid total_col and total_row
|
|
@@ -8842,13 +8904,14 @@ class GetDataChunk:
|
|
|
8842
8904
|
num_row=1,
|
|
8843
8905
|
num_col=1,
|
|
8844
8906
|
)
|
|
8845
|
-
command_data: dict = response["queries"][0]["command"]
|
|
8846
8907
|
|
|
8847
|
-
data_cols_count =
|
|
8848
|
-
data_rows_count =
|
|
8849
|
-
inter_total_hidden_dimensions =
|
|
8850
|
-
|
|
8851
|
-
|
|
8908
|
+
data_cols_count = self.h.parse_result(response, "total_col")
|
|
8909
|
+
data_rows_count = self.h.parse_result(response, "total_row")
|
|
8910
|
+
inter_total_hidden_dimensions = self.h.parse_result(
|
|
8911
|
+
response, "inter_total_hidden_dimensions"
|
|
8912
|
+
)
|
|
8913
|
+
show_global_horz_total = self.h.parse_result(response, "show_global_horz_total")
|
|
8914
|
+
show_global_vert_total = self.h.parse_result(response, "show_global_horz_total")
|
|
8852
8915
|
|
|
8853
8916
|
extra_info = {
|
|
8854
8917
|
"data_cols_count": data_cols_count,
|
|
@@ -8867,13 +8930,18 @@ class GetDataChunk:
|
|
|
8867
8930
|
num_row=1,
|
|
8868
8931
|
num_col=data_cols_count,
|
|
8869
8932
|
)
|
|
8870
|
-
command_data: dict = response["queries"][0]["command"]
|
|
8871
8933
|
|
|
8872
|
-
top_dims = [
|
|
8873
|
-
|
|
8934
|
+
top_dims = [
|
|
8935
|
+
dimensions[dim_id]["name"]
|
|
8936
|
+
for dim_id in self.h.parse_result(response, "top_dims")
|
|
8937
|
+
]
|
|
8938
|
+
left_dims = [
|
|
8939
|
+
dimensions[dim_id]["name"]
|
|
8940
|
+
for dim_id in self.h.parse_result(response, "left_dims")
|
|
8941
|
+
]
|
|
8874
8942
|
|
|
8875
8943
|
data_header = []
|
|
8876
|
-
for row in
|
|
8944
|
+
for row in self.h.parse_result(response, "top"):
|
|
8877
8945
|
tmp_data_header_row = []
|
|
8878
8946
|
for cell in row:
|
|
8879
8947
|
if cell["type"] == 4: # fact
|
|
@@ -8902,15 +8970,14 @@ class GetDataChunk:
|
|
|
8902
8970
|
num_row=units,
|
|
8903
8971
|
num_col=data_cols_count,
|
|
8904
8972
|
)
|
|
8905
|
-
command_data: dict = response["queries"][0]["command"]
|
|
8906
8973
|
left_data = [
|
|
8907
8974
|
[
|
|
8908
8975
|
{"value": cell.get("value", None), "type": cell.get("type")}
|
|
8909
8976
|
for cell in row
|
|
8910
8977
|
]
|
|
8911
|
-
for row in
|
|
8978
|
+
for row in self.h.parse_result(response, "left")
|
|
8912
8979
|
]
|
|
8913
|
-
data =
|
|
8980
|
+
data = self.h.parse_result(response, "data")
|
|
8914
8981
|
|
|
8915
8982
|
yield left_data, data
|
|
8916
8983
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
""" Модуль обработки ошибок из ответа """
|
|
3
3
|
|
|
4
4
|
import datetime
|
|
5
|
-
from datetime import datetime as dt
|
|
6
5
|
import re
|
|
6
|
+
from datetime import datetime as dt
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
9
|
from packaging.version import Version
|
|
@@ -431,9 +431,7 @@ class Validator:
|
|
|
431
431
|
:return: список дат
|
|
432
432
|
"""
|
|
433
433
|
if not filter_field_format:
|
|
434
|
-
raise ValueError(
|
|
435
|
-
f"filter_field_format must be specified!"
|
|
436
|
-
)
|
|
434
|
+
raise ValueError("filter_field_format must be specified!")
|
|
437
435
|
try:
|
|
438
436
|
start_dt = dt.strptime(start_date, filter_field_format)
|
|
439
437
|
end_dt = dt.strptime(end_date, filter_field_format)
|
|
@@ -447,7 +445,6 @@ class Validator:
|
|
|
447
445
|
if start > end:
|
|
448
446
|
raise ValueError("Start date can not be more than the end date!")
|
|
449
447
|
|
|
450
|
-
|
|
451
448
|
# Заполняем dates_list
|
|
452
449
|
dates_list = []
|
|
453
450
|
step = datetime.timedelta(days=1)
|
|
@@ -457,8 +454,13 @@ class Validator:
|
|
|
457
454
|
current_polymatica_version = Version(current_polymatica_version)
|
|
458
455
|
edge_version = Version("5.9.12")
|
|
459
456
|
while start <= end:
|
|
460
|
-
if
|
|
461
|
-
|
|
457
|
+
if (
|
|
458
|
+
current_polymatica_version >= edge_version
|
|
459
|
+
and "%S" in filter_field_format
|
|
460
|
+
):
|
|
461
|
+
dates_list.append(
|
|
462
|
+
self._format_datetime_with_millis(start, filter_field_format)
|
|
463
|
+
)
|
|
462
464
|
else:
|
|
463
465
|
dates_list.append(start.strftime(filter_field_format))
|
|
464
466
|
start += step
|
|
@@ -632,19 +634,19 @@ class Validator:
|
|
|
632
634
|
increment_dim,
|
|
633
635
|
interval_dim,
|
|
634
636
|
interval_borders,
|
|
635
|
-
encoding
|
|
637
|
+
encoding,
|
|
636
638
|
):
|
|
637
639
|
"""
|
|
638
640
|
Проверка множества аргументов функции create_sphere.
|
|
639
641
|
:param update_params: параметры обновления
|
|
640
642
|
:param file_type: тип файла
|
|
641
643
|
:param sql_params: параметры SQL
|
|
642
|
-
:param user_interval: интервал
|
|
644
|
+
:param user_interval: интервал обновлений
|
|
643
645
|
:param source_name: имя источника
|
|
644
646
|
:param cube_name: имя куба
|
|
645
647
|
:param time_zones: временные зоны
|
|
646
|
-
:param increment_dim:
|
|
647
|
-
:param interval_dim:
|
|
648
|
+
:param increment_dim: размерность для инкрементального обновления
|
|
649
|
+
:param interval_dim: размерность для интервального обновления
|
|
648
650
|
:param interval_borders: границы интервала
|
|
649
651
|
:return: имя куба
|
|
650
652
|
"""
|
|
@@ -656,7 +658,7 @@ class Validator:
|
|
|
656
658
|
check_cube_name(cube_name=cube_name, forbidden_chars=CUBE_NAME_FORBIDDEN_CHARS)
|
|
657
659
|
|
|
658
660
|
# проверка заданного типа обновления и его параметров
|
|
659
|
-
self._validate_update_params(update_params,
|
|
661
|
+
self._validate_update_params(update_params=update_params, time_zones=time_zones, file_type=file_type)
|
|
660
662
|
|
|
661
663
|
# проверки инкрементального и интервального обновления
|
|
662
664
|
update_type = update_params.get("type")
|
|
@@ -680,38 +682,41 @@ class Validator:
|
|
|
680
682
|
"""
|
|
681
683
|
Проверка кодировки. Для источника формата csv она должна быть обязательно задана.
|
|
682
684
|
"""
|
|
683
|
-
if file_type ==
|
|
684
|
-
raise ValueError(
|
|
685
|
+
if file_type == "csv" and encoding == "":
|
|
686
|
+
raise ValueError("For csv source encoding must be specified!")
|
|
685
687
|
|
|
686
|
-
def _validate_update_params(self, update_params,
|
|
688
|
+
def _validate_update_params(self, update_params, time_zones, file_type=None):
|
|
687
689
|
"""
|
|
688
690
|
Проверка параметров обновления.
|
|
689
691
|
:param update_params: параметры обновления
|
|
690
|
-
:param file_type: тип
|
|
692
|
+
:param file_type: тип файла, только для метода create_sphere.
|
|
691
693
|
:param time_zones: временные зоны
|
|
692
694
|
"""
|
|
693
695
|
update_type = update_params.get("type")
|
|
694
696
|
if update_type not in UPDATE_TYPES:
|
|
695
697
|
raise ValueError(f"Unknown update type: {update_type}")
|
|
696
|
-
if file_type in ("excel", "csv") and update_type != "ручное":
|
|
698
|
+
if file_type is not None and file_type in ("excel", "csv") and update_type != "ручное":
|
|
697
699
|
raise ValueError(
|
|
698
|
-
"For multispheres created from file sources, only
|
|
700
|
+
"For multispheres created from file sources, only full update is available!"
|
|
699
701
|
)
|
|
700
702
|
if update_type != "ручное":
|
|
701
703
|
self._validate_schedule(
|
|
702
704
|
update_params.get("schedule", list()), update_type, time_zones
|
|
703
705
|
)
|
|
706
|
+
if update_type == "по расписанию" and not update_params.get("schedule"):
|
|
707
|
+
raise ValueError(
|
|
708
|
+
"Update_params must include a schedule!"
|
|
709
|
+
)
|
|
710
|
+
|
|
704
711
|
|
|
705
712
|
@staticmethod
|
|
706
713
|
def _validate_schedule(user_schedule, update_type, time_zones):
|
|
707
714
|
"""
|
|
708
715
|
Проверка расписания.
|
|
709
|
-
:param user_schedule: расписание
|
|
716
|
+
:param user_schedule: пользовательское расписание
|
|
710
717
|
:param update_type: тип обновления
|
|
711
718
|
:param time_zones: временные зоны
|
|
712
719
|
"""
|
|
713
|
-
if user_schedule == list() and update_type == "по расписанию":
|
|
714
|
-
raise ValueError("Please enter schedule param in update_params!")
|
|
715
720
|
|
|
716
721
|
schedules = (
|
|
717
722
|
user_schedule if isinstance(user_schedule, list) else [user_schedule]
|
|
@@ -757,7 +762,7 @@ class Validator:
|
|
|
757
762
|
Проверка интервального обновления.
|
|
758
763
|
:param update_type: тип обновления
|
|
759
764
|
:param interval_dim: интервальное измерение
|
|
760
|
-
:param user_interval: интервал
|
|
765
|
+
:param user_interval: интервал обновлений
|
|
761
766
|
:param interval_borders: границы интервала
|
|
762
767
|
"""
|
|
763
768
|
if update_type == "интервальное":
|
|
@@ -834,9 +839,9 @@ class Validator:
|
|
|
834
839
|
:param new_cube_name: новое имя куба
|
|
835
840
|
:param cubes_list: список кубов
|
|
836
841
|
:param update_params: параметры обновления
|
|
837
|
-
:param user_interval: интервал
|
|
838
|
-
:param increment_dim:
|
|
839
|
-
:param interval_dim:
|
|
842
|
+
:param user_interval: интервал обновлений
|
|
843
|
+
:param increment_dim: размерность для инкрементального обновления
|
|
844
|
+
:param interval_dim: размерность для интервального обновления
|
|
840
845
|
:param interval_borders: границы интервала
|
|
841
846
|
:param time_zones: временные зоны
|
|
842
847
|
:return: кортеж (ID куба, новое имя куба)
|
|
@@ -858,16 +863,10 @@ class Validator:
|
|
|
858
863
|
)
|
|
859
864
|
|
|
860
865
|
# проверка заданного типа обновления и его параметров
|
|
861
|
-
|
|
862
|
-
if update_type not in UPDATE_TYPES:
|
|
863
|
-
raise ValueError(f"Unknown update type: {update_type}")
|
|
864
|
-
|
|
865
|
-
if update_type != "ручное":
|
|
866
|
-
self._validate_schedule(
|
|
867
|
-
update_params.get("schedule", list()), update_type, time_zones
|
|
868
|
-
)
|
|
866
|
+
self._validate_update_params(update_params=update_params, time_zones=time_zones)
|
|
869
867
|
|
|
870
868
|
# проверки инкрементального и интервального обновления
|
|
869
|
+
update_type = update_params.get("type")
|
|
871
870
|
self._validate_increment_update(update_type, increment_dim)
|
|
872
871
|
self._validate_interval_update(
|
|
873
872
|
update_type, interval_dim, user_interval, interval_borders
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|