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.
Files changed (50) hide show
  1. {polyapi-5.9.13 → polyapi-5.9.14}/PKG-INFO +1 -1
  2. {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/PKG-INFO +1 -1
  3. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/business_scenarios.py +143 -76
  4. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/consts.py +1 -0
  5. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/error_handler.py +32 -33
  6. {polyapi-5.9.13 → polyapi-5.9.14}/pyproject.toml +1 -1
  7. {polyapi-5.9.13 → polyapi-5.9.14}/setup.py +1 -1
  8. {polyapi-5.9.13 → polyapi-5.9.14}/LICENSE.txt +0 -0
  9. {polyapi-5.9.13 → polyapi-5.9.14}/README.txt +0 -0
  10. {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/SOURCES.txt +0 -0
  11. {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/dependency_links.txt +0 -0
  12. {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/requires.txt +0 -0
  13. {polyapi-5.9.13 → polyapi-5.9.14}/polyapi.egg-info/top_level.txt +0 -0
  14. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/__init__.py +0 -0
  15. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/authorization.py +0 -0
  16. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/business_logic_doc.py +0 -0
  17. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/__init__.py +0 -0
  18. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/base_command.py +0 -0
  19. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/olap_module.py +0 -0
  20. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/commands/other_modules.py +0 -0
  21. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/__init__.py +0 -0
  22. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/helper_funcs.py +0 -0
  23. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/common/params_models.py +0 -0
  24. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/exceptions.py +0 -0
  25. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/executor.py +0 -0
  26. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/__init__.py +0 -0
  27. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/base_graph.py +0 -0
  28. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/graph_interface.py +0 -0
  29. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/__init__.py +0 -0
  30. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/areas.py +0 -0
  31. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/balls.py +0 -0
  32. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/chord.py +0 -0
  33. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/circles.py +0 -0
  34. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/circles_series.py +0 -0
  35. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/corridors.py +0 -0
  36. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/cumulative_areas.py +0 -0
  37. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/cumulative_cylinders.py +0 -0
  38. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/cylinders.py +0 -0
  39. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/graph.py +0 -0
  40. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/lines.py +0 -0
  41. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/pies.py +0 -0
  42. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/point.py +0 -0
  43. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/point_series.py +0 -0
  44. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/pools.py +0 -0
  45. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/pools_3d.py +0 -0
  46. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/radar.py +0 -0
  47. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/sankey.py +0 -0
  48. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/graph/types/surface.py +0 -0
  49. {polyapi-5.9.13 → polyapi-5.9.14}/polymatica/helper.py +0 -0
  50. {polyapi-5.9.13 → polyapi-5.9.14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: polyapi
3
- Version: 5.9.13
3
+ Version: 5.9.14
4
4
  Summary: Wrapper for Polymatica API
5
5
  Home-page: https://www.polymatica.ru
6
6
  Author: Polymatica Rus LLC
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: polyapi
3
- Version: 5.9.13
3
+ Version: 5.9.14
4
4
  Summary: Wrapper for Polymatica API
5
5
  Home-page: https://www.polymatica.ru
6
6
  Author: Polymatica Rus LLC
@@ -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"Group <{group}> not found", with_traceback=False
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"Group <{group}> not found", with_traceback=False
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
- def _is_numeric(self, value: str) -> bool:
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) -> Union[List, int, int, int, int]:
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(self, measure_name: str, sort_type: str, path: List[str] = None) -> dict:
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 = module_configuration["queries"][0]["command"][
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'Path {path} is invalid! All top dims must be expanded!'
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(top: Dict, path: List[str], measure_id: str) -> Optional[int]:
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 expected_value == 'Всего' and cell.get("type") == 5 and index_cell in acceptable_indieces:
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
- break
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 cell.get("value") != expected_value and cell.get("type") in (2, 3) and acceptable_indieces_row:
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 measure_cell.get("type") == 4 and measure_cell.get("fact_id") == measure_id:
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
- h_m = user_schedule["time"].split(":")
3966
- d = datetime.datetime(1970, 1, 1, int(h_m[0]) + 3, int(h_m[1]), 0)
3967
- user_schedule["time"] = int(time.mktime(d.timetuple()))
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
- self.active_layer_id = session_layers_lst[0]["uuid"]
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
- return self.execute_manager_command(
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
- )["queries"][0]["command"]["layer"]["name"]
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
- result.append([module_id, module_name, module_type, cube_id])
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: нужно ли расширять окно мультисферы (True - нужно, False - не нужно). По-умолчанию False.
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
- ["ручное", "по расписанию", "интервальное", "инкрементальное", "undefined"];
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
- schedule = self.h.parse_result(
7660
- metadata_result, "schedule", default_value=dict()
7661
- )
7662
- if schedule and len(schedule.get("items", [])) > 0:
7663
- return "по расписанию"
7664
- return "ручное"
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
- if not self.left_dims:
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
- return self._raise_exception(
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["queries"][0]["command"]["total_row"]
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["queries"][0]["command"]["dimensions"]
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["queries"][0]["command"]["facts"]
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 = command_data["total_col"]
8848
- data_rows_count = command_data["total_row"]
8849
- inter_total_hidden_dimensions = command_data["inter_total_hidden_dimensions"]
8850
- show_global_horz_total = command_data["show_global_horz_total"]
8851
- show_global_vert_total = command_data["show_global_horz_total"]
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 = [dimensions[dim_id]["name"] for dim_id in command_data["top_dims"]]
8873
- left_dims = [dimensions[dim_id]["name"] for dim_id in command_data["left_dims"]]
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 command_data["top"]:
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 command_data["left"]
8978
+ for row in self.h.parse_result(response, "left")
8912
8979
  ]
8913
- data = command_data["data"]
8980
+ data = self.h.parse_result(response, "data")
8914
8981
 
8915
8982
  yield left_data, data
8916
8983
 
@@ -189,4 +189,5 @@ FORMAT_SETTINGS_KEYS = [
189
189
  ]
190
190
  API_VERSION = "v2"
191
191
  ROOT_PARENT = "00000000"
192
+ UNITS_LOAD_DATA_CHUNK = 1000
192
193
  BASE_LAYER_NAME = "Слой"
@@ -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 current_polymatica_version >= edge_version and "%S" in filter_field_format:
461
- dates_list.append(self._format_datetime_with_millis(start, filter_field_format))
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, file_type, time_zones)
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 == 'csv' and encoding == "":
684
- raise ValueError(f"For csv source encoding must be specified!")
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, file_type, time_zones):
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 manual update is available!"
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
- update_type = update_params.get("type")
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "polyapi"
3
- version = "5.9.13"
3
+ version = "5.9.14"
4
4
  description = "Wrapper for Polymatica API"
5
5
  license = "MIT"
6
6
  keywords = ["polymatica"]
@@ -10,7 +10,7 @@ classifiers = [
10
10
 
11
11
  setup(
12
12
  name='polyapi',
13
- version='5.9.13',
13
+ version='5.9.14',
14
14
  description='Wrapper for Polymatica API',
15
15
  long_description=open('README.txt', encoding='utf-8').read(),
16
16
  url='https://www.polymatica.ru',
File without changes
File without changes
File without changes
File without changes