polyapi 5.9.16__tar.gz → 5.9.17__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.16 → polyapi-5.9.17}/PKG-INFO +1 -1
- {polyapi-5.9.16 → polyapi-5.9.17}/polyapi.egg-info/PKG-INFO +1 -1
- {polyapi-5.9.16 → polyapi-5.9.17}/polyapi.egg-info/SOURCES.txt +6 -1
- {polyapi-5.9.16 → polyapi-5.9.17}/polyapi.egg-info/top_level.txt +1 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/business_scenarios.py +192 -62
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/error_handler.py +13 -8
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/graph_interface.py +15 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/pyproject.toml +6 -1
- {polyapi-5.9.16 → polyapi-5.9.17}/setup.py +1 -1
- polyapi-5.9.17/tests/__init__.py +1 -0
- polyapi-5.9.17/tests/conftest.py +65 -0
- polyapi-5.9.17/tests/const.py +312 -0
- polyapi-5.9.17/tests/test_create_sphere.py +735 -0
- polyapi-5.9.17/tests/test_update_cube.py +594 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/LICENSE.txt +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/README.md +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polyapi.egg-info/dependency_links.txt +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polyapi.egg-info/requires.txt +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/__init__.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/authorization.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/business_logic_doc.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/commands/__init__.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/commands/base_command.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/commands/olap_module.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/commands/other_modules.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/common/__init__.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/common/consts.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/common/helper_funcs.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/common/params_models.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/exceptions.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/executor.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/__init__.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/base_graph.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/__init__.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/areas.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/balls.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/chord.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/circles.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/circles_series.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/corridors.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/cumulative_areas.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/cumulative_cylinders.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/cylinders.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/graph.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/lines.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/pies.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/point.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/point_series.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/pools.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/pools_3d.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/radar.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/sankey.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/graph/types/surface.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/polymatica/helper.py +0 -0
- {polyapi-5.9.16 → polyapi-5.9.17}/setup.cfg +0 -0
|
@@ -46,4 +46,9 @@ polymatica/graph/types/pools.py
|
|
|
46
46
|
polymatica/graph/types/pools_3d.py
|
|
47
47
|
polymatica/graph/types/radar.py
|
|
48
48
|
polymatica/graph/types/sankey.py
|
|
49
|
-
polymatica/graph/types/surface.py
|
|
49
|
+
polymatica/graph/types/surface.py
|
|
50
|
+
tests/__init__.py
|
|
51
|
+
tests/conftest.py
|
|
52
|
+
tests/const.py
|
|
53
|
+
tests/test_create_sphere.py
|
|
54
|
+
tests/test_update_cube.py
|
|
@@ -1214,6 +1214,14 @@ class BusinessLogic:
|
|
|
1214
1214
|
def put_dim_filter_by_value(
|
|
1215
1215
|
self, value: str, dim_id: str, clear_filter: bool = False
|
|
1216
1216
|
):
|
|
1217
|
+
"""
|
|
1218
|
+
Установка фильтра по значению элемента.
|
|
1219
|
+
В первом вызове метода необходимо выставить clear_filter=True, иначе фильтр не применится.
|
|
1220
|
+
:param value: (str) Значение, по которому происходит фильтрация.
|
|
1221
|
+
:param dim_id: (str) ID размерности, по которой производится фильтрация.
|
|
1222
|
+
:param clear_filter: (bool) снять ли все отметки перед наложением фильтра, по умолчанию False.
|
|
1223
|
+
:return: (dict) результат выполнения команд (("filter", "apply_data"), ("filter", "set")).
|
|
1224
|
+
"""
|
|
1217
1225
|
if clear_filter:
|
|
1218
1226
|
self.execute_olap_command(
|
|
1219
1227
|
command_name="filter", state="filter_all_flag", dimension=dim_id
|
|
@@ -1246,14 +1254,14 @@ class BusinessLogic:
|
|
|
1246
1254
|
query = self.olap_command.collect_request(command1, command2)
|
|
1247
1255
|
|
|
1248
1256
|
try:
|
|
1249
|
-
self.exec_request.execute_request(query)
|
|
1257
|
+
result = self.exec_request.execute_request(query)
|
|
1250
1258
|
except Exception as e:
|
|
1251
1259
|
return self._raise_exception(PolymaticaException, str(e))
|
|
1252
1260
|
|
|
1253
1261
|
self.update_total_row()
|
|
1254
1262
|
self.func_name = "put_dim_filter_by_value"
|
|
1255
1263
|
|
|
1256
|
-
return
|
|
1264
|
+
return result
|
|
1257
1265
|
|
|
1258
1266
|
@timing
|
|
1259
1267
|
def put_dim_filter(
|
|
@@ -1577,7 +1585,7 @@ class BusinessLogic:
|
|
|
1577
1585
|
:param module: (str) название/идентификатор OLAP-модуля, в котором нужно переименовать группу фактов;
|
|
1578
1586
|
если модуль указан, но такого нет - сгенерируется исключение;
|
|
1579
1587
|
если модуль не указан, то берётся текущий (активный) модуль (если его нет - сгенерируется исключение).
|
|
1580
|
-
:return: (dict) результат команды ("fact", "
|
|
1588
|
+
:return: (dict) результат команды ("fact", "tree_rename_group_request").
|
|
1581
1589
|
:call_example:
|
|
1582
1590
|
1. Инициализируем класс БЛ: bl_test = BusinessLogic(login="login", password="password", url="url")
|
|
1583
1591
|
2. Вызов метода:
|
|
@@ -1811,28 +1819,49 @@ class BusinessLogic:
|
|
|
1811
1819
|
log("End download file")
|
|
1812
1820
|
|
|
1813
1821
|
@timing
|
|
1814
|
-
def export(self, path: str, file_format: str) -> Tuple[str, str]:
|
|
1822
|
+
def export(self, path: str, file_format: str, mode: str = "standard") -> Tuple[str, str]:
|
|
1815
1823
|
"""
|
|
1816
|
-
Экспортировать мультисферу в файл в заданную директорию. Если указанной директории не
|
|
1824
|
+
Экспортировать мультисферу в файл в заданную директорию. Если указанной директории не существует - она будет
|
|
1817
1825
|
создана. Непосредственно имя файла будет сгенерировано автоматически.
|
|
1818
1826
|
:param path: (str) директория, в которой нужно сохранить файл; также, директория не может быть пустой
|
|
1819
1827
|
(т.е. не может содержать пустую строку или None).
|
|
1820
|
-
:param file_format: (str) формат сохраненного файла: "csv", "
|
|
1821
|
-
:
|
|
1822
|
-
|
|
1828
|
+
:param file_format: (str) формат сохраненного файла: "csv", "xlsx", "ods", "json".
|
|
1829
|
+
:param mode: (str) режим экспорта. Возможные значения: standard — для выбора стандартного режима,
|
|
1830
|
+
сохраняющего текущий порядок строк и столбцов в выгружаемом OLAP-модуле; fast — для выбора ускоренного
|
|
1831
|
+
режима, не сохраняющего текущий порядок строк и столбцов. Необязательный аргумент.
|
|
1832
|
+
Значение по умолчанию — standard.
|
|
1833
|
+
:return (Tuple[str, str]): (file_name, path) - название файла, путь к файлу
|
|
1823
1834
|
"""
|
|
1824
1835
|
# проверки
|
|
1825
1836
|
try:
|
|
1826
|
-
self.checks(self.func_name, file_format, path)
|
|
1837
|
+
self.checks(self.func_name, file_format, path, mode)
|
|
1827
1838
|
except Exception as e:
|
|
1828
1839
|
return self._raise_exception(ValueError, str(e), with_traceback=False)
|
|
1829
1840
|
|
|
1841
|
+
# загружаем форматы фактов и преобразуем их для запроса
|
|
1842
|
+
measure_formats = self.get_measure_format()
|
|
1843
|
+
measure_precisions = []
|
|
1844
|
+
measure_units = []
|
|
1845
|
+
for measure_name, measure_format in measure_formats.items():
|
|
1846
|
+
measure_id = self.get_measure_id(measure_name)
|
|
1847
|
+
measure_precision = measure_format.get("precision", 2)
|
|
1848
|
+
measure_unit = measure_format.get("measureUnit", "")
|
|
1849
|
+
measure_precisions.append({"key": measure_id, "value": int(measure_precision)})
|
|
1850
|
+
measure_units.append({"key": measure_id, "value": measure_unit})
|
|
1851
|
+
|
|
1852
|
+
# преобразуем другие параметры для корректности запроса
|
|
1853
|
+
file_format = "xls" if file_format == "xlsx" else file_format
|
|
1854
|
+
disable_sorting = True if mode == "fast" else False
|
|
1855
|
+
|
|
1830
1856
|
# начать экспорт данных и дождаться загрузки
|
|
1831
1857
|
self.execute_olap_command(
|
|
1832
1858
|
command_name="xls_export",
|
|
1833
1859
|
state="start",
|
|
1834
1860
|
export_format=file_format,
|
|
1835
1861
|
export_destination_type="local",
|
|
1862
|
+
disable_sorting=disable_sorting,
|
|
1863
|
+
facts_precision=measure_precisions,
|
|
1864
|
+
measure_units=measure_units,
|
|
1836
1865
|
)
|
|
1837
1866
|
need_check_progress = True
|
|
1838
1867
|
while need_check_progress:
|
|
@@ -1850,11 +1879,11 @@ class BusinessLogic:
|
|
|
1850
1879
|
"code", -1
|
|
1851
1880
|
), status_info.get("message", "Unknown error!")
|
|
1852
1881
|
log(f"Export data: status: {status_code}, progress: {progress_value}")
|
|
1853
|
-
except Exception:
|
|
1882
|
+
except Exception as e:
|
|
1854
1883
|
# если упала ошибка - не удалось получить ответ от сервера: возможно, он недоступен
|
|
1855
1884
|
return self._raise_exception(
|
|
1856
1885
|
ExportError,
|
|
1857
|
-
"Failed to export data! Possible server is unavailable.",
|
|
1886
|
+
f"Failed to export data! Possible server is unavailable. Error: {e}",
|
|
1858
1887
|
)
|
|
1859
1888
|
|
|
1860
1889
|
# анализируем статус загрузки
|
|
@@ -1882,24 +1911,22 @@ class BusinessLogic:
|
|
|
1882
1911
|
|
|
1883
1912
|
# имя файла в результате команды ('xls_export', 'check') приходит только после полной загрузки файла
|
|
1884
1913
|
# формируем название нужного файла
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
download_file_name = server_f_name.replace(":", "-")
|
|
1888
|
-
download_file_name = (
|
|
1889
|
-
download_file_name[:-8]
|
|
1890
|
-
if download_file_name.split(".")[-1] not in format_map.get(file_format)
|
|
1891
|
-
else download_file_name
|
|
1892
|
-
)
|
|
1914
|
+
server_file_name = self.h.parse_result(result=progress_data, key="file_name")
|
|
1915
|
+
download_file_name = self.h.parse_result(result=progress_data, key="file_name_hint")
|
|
1893
1916
|
|
|
1894
1917
|
# скачивание файла
|
|
1895
1918
|
self._download_file(
|
|
1896
|
-
f"{self.resources_url}/{
|
|
1919
|
+
f"{self.resources_url}/{server_file_name}", path, download_file_name
|
|
1897
1920
|
)
|
|
1898
1921
|
|
|
1899
1922
|
# проверка что файл скачался после экспорта
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1923
|
+
if download_file_name not in os.listdir(path):
|
|
1924
|
+
return self._raise_exception(
|
|
1925
|
+
ExportError,
|
|
1926
|
+
f'File "{download_file_name}" was not found in directory "{path}" after download. '
|
|
1927
|
+
f'Please check file permissions, available disk space, and directory access rights.',
|
|
1928
|
+
with_traceback=False
|
|
1929
|
+
)
|
|
1903
1930
|
return download_file_name, path
|
|
1904
1931
|
|
|
1905
1932
|
@staticmethod
|
|
@@ -2607,7 +2634,7 @@ class BusinessLogic:
|
|
|
2607
2634
|
:param user_password: (str) пароль пользователя, под которым запускается сценарий;
|
|
2608
2635
|
не нужно указывать, если требуется запустить сценарий под пользователем, по-умолчанию не имеющим пароля,
|
|
2609
2636
|
например, временный пользователь.
|
|
2610
|
-
:param units: (int) число выгружаемых строк мультисферы (по-умолчанию
|
|
2637
|
+
:param units: (int) число выгружаемых строк мультисферы (по-умолчанию 1000).
|
|
2611
2638
|
:return: (tuple) данные мультисферы и данные о колонках мультсферы (аналогично методу "get_data_frame").
|
|
2612
2639
|
:call_example:
|
|
2613
2640
|
1. Инициализируем класс БЛ: bl_test = BusinessLogic(login="login", password="password", url="url")
|
|
@@ -2730,7 +2757,7 @@ class BusinessLogic:
|
|
|
2730
2757
|
иначе будут возвращаться неполные данные.
|
|
2731
2758
|
ВАЖНО: генерация строк не учитывает промежуточные и общие итоги (тоталы) по строкам и колонкам.
|
|
2732
2759
|
:param units: (int) количество подгружаемых строк; ожидается целое положительное число больше 0;
|
|
2733
|
-
по-умолчанию
|
|
2760
|
+
по-умолчанию 1000.
|
|
2734
2761
|
:param show_all_columns: (bool) установка показа всех колонок датафрейма.
|
|
2735
2762
|
:param show_all_rows: (bool) установка показа всех строк датафрейма.
|
|
2736
2763
|
:return: (DataFrame, DataFrame) данные мультисферы и колонки мультисферы в формате DataFrame.
|
|
@@ -4086,9 +4113,13 @@ class BusinessLogic:
|
|
|
4086
4113
|
опционально также может быть задан порт подключения, в таком случае он должен идти после указания
|
|
4087
4114
|
хоста через двоеточие (например, "10.18.0.132:5433", "polymatica.database1.ru:5433");
|
|
4088
4115
|
в случае, если порт явно не указан, подразумевается порт по-умолчанию 5432.
|
|
4116
|
+
Для источника данных JDBC в этом поле необходимо указать DSN в формате,
|
|
4117
|
+
например, jdbc:mysql://192.111.11.11:3306/database. Если database стандартный, то возможно
|
|
4118
|
+
подключение и без указания его в DSN.
|
|
4089
4119
|
"login" - логин пользователя.
|
|
4090
4120
|
"passwd" - пароль пользователя.
|
|
4091
|
-
"database" - имя базы данных.
|
|
4121
|
+
"database" - имя базы данных. Для источника данных JDBC указывать "database" не надо, оно указывается
|
|
4122
|
+
после хоста в строке DSN (см. описание поля "server")
|
|
4092
4123
|
"sql_query" - запрос, который необходимо выполнить на сервере.
|
|
4093
4124
|
Пример задания параметра:
|
|
4094
4125
|
{
|
|
@@ -4115,7 +4146,13 @@ class BusinessLogic:
|
|
|
4115
4146
|
при этом второе значение должно быть больше первого. Формат значений времени: "DD.MM.YYYY". Все остальные
|
|
4116
4147
|
значения, если они будут переданы, будут игнорироваться. Актуально только для интервального обновления.
|
|
4117
4148
|
:param encoding: (str) кодировка, например UTF-8; обязательна для csv-источника.
|
|
4118
|
-
:param delayed: (bool)
|
|
4149
|
+
:param delayed: (bool) параметр, определяющий, будет ли отложено создание мультисферы.
|
|
4150
|
+
Если False, то не будет, и мультисфера будет автоматически создана.
|
|
4151
|
+
Если True: если настроено расписание обновлений (schedule в update_params),
|
|
4152
|
+
то импорт данных мультисферы начнется при первом срабатывании заданного расписания,
|
|
4153
|
+
а если расписание не настроено, то импорт данных мультисферы начнется при следующем
|
|
4154
|
+
принудительном обновлении (через метод manual_update_cube или через веб-интерфейс).
|
|
4155
|
+
По умолчанию False.
|
|
4119
4156
|
:param modified_records_params: (dict) параметры обновления для типа "обновление измененных записей".
|
|
4120
4157
|
Поля, передаваемые в словарь:
|
|
4121
4158
|
"modified_records_key" - поле, которому осуществляется сопоставление данных (имя размерности).
|
|
@@ -4197,7 +4234,12 @@ class BusinessLogic:
|
|
|
4197
4234
|
modified_records_params = dict()
|
|
4198
4235
|
else:
|
|
4199
4236
|
copy.deepcopy(modified_records_params)
|
|
4200
|
-
|
|
4237
|
+
algo_version = modified_records_params.get("version")
|
|
4238
|
+
if algo_version is None:
|
|
4239
|
+
modified_records_params["version"] = 1
|
|
4240
|
+
elif algo_version not in (0, 1):
|
|
4241
|
+
self.logger.warning(f"Param 'modified_records_algo_version' must be 0 or 1, "
|
|
4242
|
+
f"not {algo_version}. Changed to 1")
|
|
4201
4243
|
modified_records_params["version"] = 1
|
|
4202
4244
|
|
|
4203
4245
|
# проверки
|
|
@@ -4392,7 +4434,7 @@ class BusinessLogic:
|
|
|
4392
4434
|
"datetime",
|
|
4393
4435
|
):
|
|
4394
4436
|
error_msg = (
|
|
4395
|
-
f'Dimension "{
|
|
4437
|
+
f'Dimension "{increment_dim}" has type "{current_type}", '
|
|
4396
4438
|
f'one of types is expected: ["uint8", "uint16", "uint32", '
|
|
4397
4439
|
f'"uint64", "double", "date", "time", "datetime"]!'
|
|
4398
4440
|
)
|
|
@@ -4584,7 +4626,13 @@ class BusinessLogic:
|
|
|
4584
4626
|
используется для замены файла в источнике. Можно заменять на файл того же типа, что и был,
|
|
4585
4627
|
например "csv" на "csv". В пути файла обязательно должно быть расширение, например, ".csv".
|
|
4586
4628
|
:param separator: (str) разделитель столбцов. Обязателен для csv-источника (при замене источника).
|
|
4587
|
-
:param delayed: (bool)
|
|
4629
|
+
:param delayed: (bool) параметр, определяющий, будет ли отложено обновление мультисферы.
|
|
4630
|
+
Если False, то не будет, и мультисфера будет автоматически обновлена.
|
|
4631
|
+
Если True: если настроено расписание обновлений (schedule в update_params),
|
|
4632
|
+
то мультисфера обновится при первом срабатывании заданного расписания, а если расписание не настроено,
|
|
4633
|
+
то мультисфера обновится при следующем принудительном обновлении
|
|
4634
|
+
(через метод manual_update_cube или через веб-интерфейс).
|
|
4635
|
+
По умолчанию False.
|
|
4588
4636
|
:param increment_dim: (str) название размерности, необходимой для инкрементального обновления.
|
|
4589
4637
|
:param interval_dim: (str) название размерности для интервального обновления; размерность должна иметь
|
|
4590
4638
|
один из следующих типов: date, datetime.
|
|
@@ -4673,7 +4721,12 @@ class BusinessLogic:
|
|
|
4673
4721
|
modified_records_params = dict()
|
|
4674
4722
|
else:
|
|
4675
4723
|
copy.deepcopy(modified_records_params)
|
|
4676
|
-
|
|
4724
|
+
algo_version = modified_records_params.get("version")
|
|
4725
|
+
if algo_version is None:
|
|
4726
|
+
modified_records_params["version"] = 1
|
|
4727
|
+
elif algo_version not in (0, 1):
|
|
4728
|
+
self.logger.warning(f"Param 'modified_records_algo_version' must be 0 or 1, "
|
|
4729
|
+
f"not {algo_version}. Changed to 1")
|
|
4677
4730
|
modified_records_params["version"] = 1
|
|
4678
4731
|
cubes_list = self.get_cubes_list()
|
|
4679
4732
|
self.func_name = "update_cube"
|
|
@@ -5138,12 +5191,14 @@ class BusinessLogic:
|
|
|
5138
5191
|
dim_elems = self.h.parse_result(res, dim_items_key)
|
|
5139
5192
|
|
|
5140
5193
|
# вытащить идентификатор группировки размерности (если он есть у этого элемента)
|
|
5194
|
+
# TODO: при доработке для использования метода с любым уровнем размерности вместо all_dims_ids[0] и
|
|
5195
|
+
# dim_elems[0] использовать индекс соответствующего уровня
|
|
5141
5196
|
try:
|
|
5142
5197
|
for elem in dim_elems:
|
|
5143
|
-
current_elem
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5198
|
+
for current_elem in elem:
|
|
5199
|
+
if current_elem.get("type") == 3 and current_elem.get("value") == name:
|
|
5200
|
+
group_id = current_elem["group_id"]
|
|
5201
|
+
break
|
|
5147
5202
|
except KeyError:
|
|
5148
5203
|
msg = f'No grouped dimensions with name "{name}"!'
|
|
5149
5204
|
return self._raise_exception(ValueError, msg)
|
|
@@ -5302,7 +5357,7 @@ class BusinessLogic:
|
|
|
5302
5357
|
if position == "left":
|
|
5303
5358
|
total_dim_items = [lst[0].strip() for lst in data[1 + top_dims_count :]]
|
|
5304
5359
|
else:
|
|
5305
|
-
total_dim_items = [item.strip() for item in data[0][left_dims_count
|
|
5360
|
+
total_dim_items = [item.strip() for item in data[0][left_dims_count:]]
|
|
5306
5361
|
|
|
5307
5362
|
# проверяем, все ли элементы размерности, указанные пользователем, есть в общем списке элементов
|
|
5308
5363
|
# если текущий элемент есть - сохраняем его индекс, в противном случае - генерируем ошибку
|
|
@@ -6106,7 +6161,7 @@ class BusinessLogic:
|
|
|
6106
6161
|
|
|
6107
6162
|
result = self.execute_olap_command(
|
|
6108
6163
|
command_name="view",
|
|
6109
|
-
state="
|
|
6164
|
+
state="select_change",
|
|
6110
6165
|
position=1,
|
|
6111
6166
|
level=len(dim_values) - 1,
|
|
6112
6167
|
line=line,
|
|
@@ -6133,7 +6188,7 @@ class BusinessLogic:
|
|
|
6133
6188
|
чтобы перед вызовом метода были развёрнуты все узлы мультисферы.
|
|
6134
6189
|
3. Все тоталы (как промежуточные, так и итоговые) генератором выведены не будут вне зависимости от того,
|
|
6135
6190
|
включены они или нет.
|
|
6136
|
-
:param units: (int) количество подгружаемых строк; по-умолчанию
|
|
6191
|
+
:param units: (int) количество подгружаемых строк; по-умолчанию 1000.
|
|
6137
6192
|
:param convert_type: (bool) нужно ли преобразовывать данные из типов, определённых Полиматикой, к Python-типам;
|
|
6138
6193
|
по-умолчанию False (т.е. не нужно).
|
|
6139
6194
|
:param default_value: (Any) актуален только при convert_type = True;
|
|
@@ -7895,7 +7950,6 @@ class BusinessLogic:
|
|
|
7895
7950
|
},
|
|
7896
7951
|
...
|
|
7897
7952
|
]
|
|
7898
|
-
В случае, если мультисфер в сценарии нет, вернётся пустой список.
|
|
7899
7953
|
В случае, если в мультисфере нет вынесенных размерностей, то в "used_dimensions"
|
|
7900
7954
|
будет пустой список.
|
|
7901
7955
|
"""
|
|
@@ -8161,14 +8215,29 @@ class BusinessLogic:
|
|
|
8161
8215
|
'is_composite': <value>, # является ли размерность составной; для факта - None
|
|
8162
8216
|
'position': <value>, # позиция размерности ("left"/"up"/"out"); для факта - None
|
|
8163
8217
|
'have_filter': <value>, # наложен ли фильтр на размерность; для факта - None,
|
|
8164
|
-
'visible': <value>, # видимость
|
|
8218
|
+
'visible': <value>, # видимость размерности (используется при расчете факта
|
|
8219
|
+
по фиксированной размерности) или факта
|
|
8165
8220
|
'horizontal': <value>, # включён ли горизонтальный расчёт для факта; для размерности - None
|
|
8166
8221
|
'is_calculated': <value>, # является ли факт вычислимым; для размерности - None
|
|
8167
|
-
'is_group':
|
|
8222
|
+
'is_group': False, # является ли факт группировкой других фактов; для размерности - None
|
|
8168
8223
|
'group_id': <value>, # идентификатор группы, в которую ходит данный факт;
|
|
8169
8224
|
если факт не входит ни в какую группу, то None;
|
|
8170
8225
|
для размерности - None
|
|
8171
|
-
'type': <value
|
|
8226
|
+
'type': <value>, # тип факта; для размерности - None
|
|
8227
|
+
'level': <value>, # уровень, на который вынесена размерность или уровень для установки
|
|
8228
|
+
расчёта сложного вида факта по уровню
|
|
8229
|
+
'is_shown': <value>, # флаг скрытых размерностей или фактов, флаг, сообщающий о том, что
|
|
8230
|
+
у пользователя не должно быть прямой возможности взаимодействовать
|
|
8231
|
+
с этой размерностью или фактом
|
|
8232
|
+
'level_fixed_dim': <value>,# идентификатор опорной размерности, по которой производится расчет
|
|
8233
|
+
сложного факта. Для простого, относительного факта или сложного факта
|
|
8234
|
+
с расчетом по уровню отображается значение '00000000';
|
|
8235
|
+
для размерности - None
|
|
8236
|
+
'is_level_fixed': <value>, # признак расчета по опорной размерности. Для сложного факта с расчетом
|
|
8237
|
+
по опорной размерности — True. Для простого, относительного факта или
|
|
8238
|
+
сложного факта с расчетом по уровню — False;
|
|
8239
|
+
для размерности - None
|
|
8240
|
+
'selected': <value>, # выбран ли факт; для размерности - None
|
|
8172
8241
|
}
|
|
8173
8242
|
Группы фактов представляют собой словарь следующего формата:
|
|
8174
8243
|
{
|
|
@@ -8176,13 +8245,22 @@ class BusinessLogic:
|
|
|
8176
8245
|
'type': 'group', # тип: группа
|
|
8177
8246
|
'name': <value>, # имя группы
|
|
8178
8247
|
'visibility': 'visible', # видимость группы
|
|
8248
|
+
'is_group': True, # признак группы
|
|
8179
8249
|
'nodes': # узлы (факты и группы), входящие в группу, в формате списка словарей
|
|
8180
8250
|
[
|
|
8181
8251
|
{
|
|
8182
|
-
'id': идентификатор
|
|
8252
|
+
'id': идентификатор узла - подгруппы,
|
|
8253
|
+
'type': тип, для подгруппы - 'group',
|
|
8254
|
+
'name': имя подгруппы,
|
|
8255
|
+
'visibility': видимость подгруппы,
|
|
8256
|
+
'nodes': узлы (факты и группы), входящие в подгруппу,
|
|
8257
|
+
в формате списка словарей
|
|
8258
|
+
},
|
|
8259
|
+
{
|
|
8260
|
+
'id': идентификатор узла - факта,
|
|
8183
8261
|
'type': тип, для факта - 'measure',
|
|
8184
8262
|
'measure': идентификатор самого факта,
|
|
8185
|
-
group_id': идентификатор группы, в которую входит этот узел
|
|
8263
|
+
'group_id': идентификатор группы, в которую входит этот узел
|
|
8186
8264
|
},
|
|
8187
8265
|
{...}
|
|
8188
8266
|
]
|
|
@@ -8197,25 +8275,30 @@ class BusinessLogic:
|
|
|
8197
8275
|
# получаем список размерностей и фактов
|
|
8198
8276
|
dimensions, measures = self._get_dimensions_list(), self._get_measures_list()
|
|
8199
8277
|
dim_base_dict = dict.fromkeys(
|
|
8200
|
-
["
|
|
8201
|
-
|
|
8202
|
-
)
|
|
8203
|
-
measure_base_dict = dict.fromkeys(
|
|
8204
|
-
["data_type", "is_composite", "position", "have_filter"], None
|
|
8278
|
+
["horizontal", "is_calculated", "is_group", "group_id", "type", "level_fixed_dim", "is_level_fixed",
|
|
8279
|
+
"selected"], None,
|
|
8205
8280
|
)
|
|
8281
|
+
measure_base_dict = {
|
|
8282
|
+
**dict.fromkeys(["data_type", "is_composite", "position", "have_filter"], None),
|
|
8283
|
+
"is_group": False,
|
|
8284
|
+
}
|
|
8206
8285
|
|
|
8207
8286
|
# конфигурация размерностей
|
|
8208
8287
|
for dimension in dimensions:
|
|
8209
8288
|
current_dim_dict = {
|
|
8210
8289
|
"name": dimension.get("name"),
|
|
8211
8290
|
"id": dimension.get("id"),
|
|
8212
|
-
"is_copy": dimension.get("base_id") !=
|
|
8291
|
+
"is_copy": dimension.get("base_id") != EMPTY_ID,
|
|
8213
8292
|
"data_type": TYPES_MAP.get(
|
|
8214
8293
|
POLYMATICA_INT_TYPES_MAP.get(dimension.get("olap_type"))
|
|
8215
8294
|
),
|
|
8216
8295
|
"is_composite": dimension.get("olap3_type") == 3,
|
|
8217
8296
|
"position": POSITION_MAP.get(dimension.get("position")),
|
|
8218
8297
|
"have_filter": dimension.get("haveFilter"),
|
|
8298
|
+
"level": dimension.get("level"),
|
|
8299
|
+
"visible": dimension.get("visible"),
|
|
8300
|
+
"is_shown": dimension.get("is_shown"),
|
|
8301
|
+
|
|
8219
8302
|
}
|
|
8220
8303
|
current_dim_dict.update(dim_base_dict)
|
|
8221
8304
|
olap_config["dimensions"].append(current_dim_dict)
|
|
@@ -8226,24 +8309,30 @@ class BusinessLogic:
|
|
|
8226
8309
|
nodes_list
|
|
8227
8310
|
)
|
|
8228
8311
|
for measure in measures:
|
|
8312
|
+
node = nodes_dict_with_group[measure["id"]]
|
|
8229
8313
|
current_measure_dict = {
|
|
8230
8314
|
"name": measure.get("name"),
|
|
8231
8315
|
"id": measure.get("id"),
|
|
8232
|
-
"is_copy": measure.get("base_id") !=
|
|
8316
|
+
"is_copy": measure.get("base_id") != EMPTY_ID,
|
|
8233
8317
|
"visible": measure.get("visible"),
|
|
8234
8318
|
"horizontal": measure.get("horizontal"),
|
|
8235
8319
|
"is_calculated": measure.get("olap3_type") == 3,
|
|
8236
|
-
"
|
|
8237
|
-
"group_id": measure.get("fgroup_id"), # deprecated
|
|
8320
|
+
"group_id": node["group_id"],
|
|
8238
8321
|
"type": MEASURE_INT_STR_TYPES_MAP.get(measure.get("plm_type")),
|
|
8322
|
+
"level": measure.get("level"),
|
|
8323
|
+
"level_fixed_dim": measure.get("level_fixed_dim"),
|
|
8324
|
+
"is_level_fixed": measure.get("is_level_fixed"),
|
|
8325
|
+
"selected": measure.get("selected"),
|
|
8326
|
+
"is_shown": measure.get("is_shown"),
|
|
8239
8327
|
}
|
|
8240
|
-
|
|
8241
|
-
current_measure_dict["group_id"] = node["group_id"]
|
|
8328
|
+
|
|
8242
8329
|
current_measure_dict.update(measure_base_dict)
|
|
8243
8330
|
olap_config["measures"].append(current_measure_dict)
|
|
8244
8331
|
|
|
8245
8332
|
# добавляем группы фактов в список фактов
|
|
8246
8333
|
measures_groups = [node for node in nodes_list if node.get("type") == "group"]
|
|
8334
|
+
for group_node in measures_groups:
|
|
8335
|
+
group_node["is_group"] = True
|
|
8247
8336
|
olap_config["measures"].extend(measures_groups)
|
|
8248
8337
|
# меняем фокус на исходную мультисферу и возвращаем данные
|
|
8249
8338
|
self.set_multisphere_module_id(current_ms_id)
|
|
@@ -8537,10 +8626,21 @@ class BusinessLogic:
|
|
|
8537
8626
|
url: str,
|
|
8538
8627
|
method: str,
|
|
8539
8628
|
headers: dict,
|
|
8540
|
-
cookies=None,
|
|
8541
|
-
data=None,
|
|
8542
|
-
json: dict = None,
|
|
8629
|
+
cookies: Optional[dict] = None,
|
|
8630
|
+
data: Optional[Union[dict, List[Tuple], bytes]] = None,
|
|
8631
|
+
json: Optional[dict] = None,
|
|
8543
8632
|
):
|
|
8633
|
+
"""
|
|
8634
|
+
Выполнить HTTP-запрос к API_v2 Аналитикс.
|
|
8635
|
+
|
|
8636
|
+
:param url: (str) URL-адрес стенда Аналитикс.
|
|
8637
|
+
:param method: (str) HTTP-метод (например, "GET", "PATCH", "POST", "PUT", "DELETE").
|
|
8638
|
+
:param headers: (dict) Заголовки HTTP-запроса.
|
|
8639
|
+
:param cookies: (dict) Куки запроса; по умолчанию `{ "session": self.session_id }`.
|
|
8640
|
+
:param data: (dict | list of tuples | bytes) Тело запроса.
|
|
8641
|
+
:param json: (dict) Тело запроса в формате JSON.
|
|
8642
|
+
:return: `requests.models.Response` — необработанный ответ библиотеки `requests`.
|
|
8643
|
+
"""
|
|
8544
8644
|
if cookies is None:
|
|
8545
8645
|
cookies = {"session": self.session_id}
|
|
8546
8646
|
|
|
@@ -8557,7 +8657,8 @@ class BusinessLogic:
|
|
|
8557
8657
|
def switch_session(self, new_owner_uuid: str) -> int:
|
|
8558
8658
|
"""
|
|
8559
8659
|
Переключить владельца сессии.
|
|
8560
|
-
:param new_owner_uuid: (str)
|
|
8660
|
+
:param new_owner_uuid: (str) uuid - идентификатор пользователя, который должен стать владельцем текущей сессии.
|
|
8661
|
+
Можно получить uuid с помощью метода get_user_uuid(login).
|
|
8561
8662
|
:return: (int) статус код:
|
|
8562
8663
|
Статусы ответа:
|
|
8563
8664
|
204 No Content - успешно.
|
|
@@ -8579,10 +8680,39 @@ class BusinessLogic:
|
|
|
8579
8680
|
response = self.request_to_api_v2(
|
|
8580
8681
|
url=url, method="PATCH", headers=headers, json=data
|
|
8581
8682
|
)
|
|
8582
|
-
|
|
8583
|
-
|
|
8683
|
+
if response.status_code != 204:
|
|
8684
|
+
response_reasons_mapping = {
|
|
8685
|
+
400: "Bad Request (wrong uuid format).",
|
|
8686
|
+
401: "Request from unauthorized user.",
|
|
8687
|
+
403: "The current user does not have admin role.",
|
|
8688
|
+
404: f"User with uuid {new_owner_uuid} not found.",
|
|
8689
|
+
500: "Internal server error."
|
|
8690
|
+
}
|
|
8691
|
+
self.logger.error(f"Response status code != 204, session was not switched. "
|
|
8692
|
+
f"Reason: {response_reasons_mapping.get(response.status_code)}")
|
|
8584
8693
|
return response.status_code
|
|
8585
8694
|
|
|
8695
|
+
def get_user_uuid(self, login: str = None) -> str:
|
|
8696
|
+
"""
|
|
8697
|
+
Метод для получения uuid пользователя. UUID используется в методе switch_session.
|
|
8698
|
+
:param login: (str) логин пользователя, если не указан, то используется логин текущего пользователя.
|
|
8699
|
+
:return: (str) uuid пользователя
|
|
8700
|
+
"""
|
|
8701
|
+
user_uuid = None
|
|
8702
|
+
if login is None:
|
|
8703
|
+
login = self.login
|
|
8704
|
+
users_result = self.execute_manager_command(command_name="user", state="list_request")
|
|
8705
|
+
users_data = self.h.parse_result(result=users_result, key="users")
|
|
8706
|
+
for user in users_data:
|
|
8707
|
+
if user.get('login') == login:
|
|
8708
|
+
user_uuid = user.get('uuid')
|
|
8709
|
+
if user_uuid:
|
|
8710
|
+
return user_uuid
|
|
8711
|
+
else:
|
|
8712
|
+
self._raise_exception(UserNotFoundError,
|
|
8713
|
+
f'User "{login}" not found on server {self.base_url}',
|
|
8714
|
+
with_traceback=False)
|
|
8715
|
+
|
|
8586
8716
|
@timing
|
|
8587
8717
|
def cleanup_multisphere_data(
|
|
8588
8718
|
self,
|
|
@@ -9023,7 +9153,7 @@ class GetDataChunk:
|
|
|
9023
9153
|
3. Все тоталы (как промежуточные, так и итоговые) генератором выведены не будут вне зависимости от того,
|
|
9024
9154
|
включены они или нет.
|
|
9025
9155
|
:param units: (int) количество подгружаемых строк; ожидается целое положительное число больше 0;
|
|
9026
|
-
по-умолчанию
|
|
9156
|
+
по-умолчанию 1000.
|
|
9027
9157
|
:param convert_type: (bool) нужно ли преобразовывать данные из типов, определённых Полиматикой, к Python-типам;
|
|
9028
9158
|
по-умолчанию False (т.е. не нужно).
|
|
9029
9159
|
:param default_value: (Any) актуален только при convert_type = True;
|
|
@@ -483,19 +483,22 @@ class Validator:
|
|
|
483
483
|
return base
|
|
484
484
|
|
|
485
485
|
@staticmethod
|
|
486
|
-
def check_export(file_format, file_path):
|
|
486
|
+
def check_export(file_format, file_path, mode):
|
|
487
487
|
"""
|
|
488
488
|
Проверка параметров для функции export.
|
|
489
489
|
:param file_format: формат файла
|
|
490
490
|
:param file_path: путь к файлу
|
|
491
|
+
:param mode: (str) режим экспорта
|
|
491
492
|
:return: True, если проверка прошла успешно
|
|
492
493
|
"""
|
|
493
|
-
if file_format not in ["csv", "xls", "json"]:
|
|
494
|
+
if file_format not in ["csv", "xls", "xlsx", "ods", "json"]:
|
|
494
495
|
raise ValueError(
|
|
495
|
-
f'Wrong file format: "{file_format}". Only .csv, .
|
|
496
|
+
f'Wrong file format: "{file_format}". Only .csv, .xlsx, .ods, .json formats allowed!'
|
|
496
497
|
)
|
|
497
498
|
if not file_path:
|
|
498
499
|
raise ValueError("Empty file path!")
|
|
500
|
+
if mode not in ("standard", "fast"):
|
|
501
|
+
raise ValueError(f'Param mode must be "standard" or "fast", not {mode}')
|
|
499
502
|
return True
|
|
500
503
|
|
|
501
504
|
@staticmethod
|
|
@@ -858,18 +861,20 @@ class Validator:
|
|
|
858
861
|
:param file_type: тип файла
|
|
859
862
|
:param sql_params: параметры SQL
|
|
860
863
|
"""
|
|
861
|
-
if
|
|
864
|
+
if file_type not in ("excel", "csv"):
|
|
862
865
|
if sql_params is None:
|
|
863
866
|
raise ValueError(
|
|
864
867
|
'If your sourse is sql: fill in param "sql_params"!\n\n'
|
|
865
868
|
'In other cases: it is wrong param "file_type": %s\n\nIt can be only:\n'
|
|
866
869
|
"excel OR csv" % file_type
|
|
867
870
|
)
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
+
required_keys_jdbc = {"server", "login", "passwd", "sql_query"}
|
|
872
|
+
required_keys_sql = required_keys_jdbc | {"database"}
|
|
873
|
+
required_keys = required_keys_jdbc if file_type == "jdbc" else required_keys_sql
|
|
874
|
+
missing_keys = required_keys - set(sql_params.keys())
|
|
875
|
+
if missing_keys:
|
|
871
876
|
raise ValueError(
|
|
872
|
-
"
|
|
877
|
+
f"Missing required sql_params: {', '.join(sorted(missing_keys))}"
|
|
873
878
|
)
|
|
874
879
|
|
|
875
880
|
@staticmethod
|
|
@@ -452,6 +452,21 @@ class IGraph:
|
|
|
452
452
|
raise ValueError(error_msg)
|
|
453
453
|
|
|
454
454
|
layer_id, graph_module_id = graph_module_ids[0]
|
|
455
|
+
|
|
456
|
+
# выгружаем текущие параметры графика
|
|
457
|
+
settings = self._base_bl.execute_manager_command(
|
|
458
|
+
command_name="user_iface",
|
|
459
|
+
state="load_settings",
|
|
460
|
+
module_id=graph_module_id,
|
|
461
|
+
)
|
|
462
|
+
current_module_settings = self._base_bl.h.parse_result(settings, "settings")
|
|
463
|
+
plot_name = current_module_settings.get('plotName')
|
|
464
|
+
current_state = current_module_settings['plotData'][plot_name]['state']
|
|
465
|
+
|
|
466
|
+
# подставляем текущее имя графика из настроек, если его не указали
|
|
467
|
+
if 'name' not in self._other:
|
|
468
|
+
self._other['name'] = current_state.get('title')
|
|
469
|
+
|
|
455
470
|
# получаем идентификатор OLAP-модуля, на основе которого построен график
|
|
456
471
|
layer_settings = self._base_bl.execute_manager_command(
|
|
457
472
|
command_name="user_layer", state="get_layer", layer_id=layer_id
|