Python как источник данных виджета
Python-скрипт можно использовать как источник данных виджета — вместо DAX-меры и измерения из модели. Скрипт возвращает таблицу, а виджет отрисовывает её штатными средствами (график, таблица, круговая диаграмма и т.д.).
Это позволяет:
получать данные из внешних систем по REST — для визуализации или для передачи данных во внешний сервис;
выполнять финальные преобразования и вычисления средствами библиотеки Pandas;
использовать виджет в кросс-фильтрации наравне со стандартными виджетами.
Python-режим — это не отдельный тип виджета. В этот режим можно перевести любой стандартный виджет: его источником данных становится скрипт.
Включение Python-режима
На панели «Виджеты» откройте раздел «Оформление» и включите «Python-режим». Режим доступен для всех стандартных виджетов Visiology.
Затем на вкладке «Виджеты» нажмите кнопку «Открыть редактор Python» — откроется область для работы с кодом.
Порядок настройки виджета
Включите Python-режим и откройте редактор Python.
Напишите скрипт. Результат поместите в DataFrame и верните его через
return.Нажмите «Выполнить код» — в нижней части редактора появятся колонки результата.
Переместите колонки результата в поля виджета (Ось X, Ось Y, Легенда, Значение и т.д.). Для мер выберите агрегацию (Sum, Min, Max, Count, Average, DistinctCount).
Сохраните виджет.
Возврат результата
Чтобы вернуть результат, поместите его в DataFrame и укажите после return. Поддерживаются pandas.DataFrame, polars.DataFrame и pyarrow.Table.
import pandas as pd
df = pd.DataFrame({"Категория": ["A", "B"], "Значение": [10, 20]})
return dfРезультатом скрипта должна быть таблица.
Скрипт формирует только данные. Внешний вид виджета — тип диаграммы, цвета, серии, оси, форматирование — настраивается стандартными средствами виджета, а не из скрипта. Прямого доступа к объекту визуализации из Python нет.
Доступные библиотеки
В скрипте доступны перечисленные ниже библиотеки. Их нужно импортировать обычным import; устанавливать ничего не требуется.
Библиотека | Версия | Назначение |
|---|---|---|
| 2.2.3 | таблицы; основной формат результата |
| 1.18.0 | быстрые таблицы; альтернатива pandas |
| 2.1.3 | числовые вычисления |
| 18.1.0 | колоночные данные |
| 2.32.3 | HTTP-запросы к внешним источникам |
| 0.27.2 | HTTP-запросы (синхронные и асинхронные) |
Заблокированы модули subprocess, multiprocessing, threading, _thread, ctypes, cffi — при их импорте возникает понятная ошибка (запуск процессов, фоновые потоки и вызовы нативного кода в виджетах не поддерживаются).
Дополнительные библиотеки можно подключить через администратора платформы — см. раздел «Установка сторонних библиотек».
Получение данных из модели — client.query
Функция client.query запрашивает данные из модели Visiology. Группировку и агрегацию выполняет Formula Engine — с учётом политик доступа (RLS/OLS) и фильтров дашборда.
df = client.query(
rows=["'Товары'[Категория]"], # поля-измерения по строкам
columns=["'Период'[Год]"], # поля-измерения по столбцам (необязательно)
measures={"'Продажи'[Сумма]": "sum"}, # пары «колонка: агрегация»
).to_pandas()
return dfrows,columns— поля-измерения в формате'Таблица'[Поле].measures— пары «колонка: агрегация». Допустимые агрегации:sum,min,max,count,average,distinct_count.filters— по умолчанию применяются все фильтры дашборда; при необходимости их можно переопределить (см. ниже).
Функция возвращает таблицу в формате pyarrow.Table. Чтобы работать с ней в Pandas, добавьте .to_pandas().
client.query рассчитан на агрегированные или срезанные данные. Число строк в его результате ограничено — тем же лимитом, что и выгрузка в Excel (значение задаётся в конфигурации платформы); при превышении запрос к Formula Engine не выполнится. Поэтому агрегируйте в самом запросе (задавайте агрегаты в measures), а не получайте сырые строки целиком. Подробнее — в разделе «Среда выполнения и ограничения».
Данные возвращаются с учётом прав текущего пользователя (RLS/OLS) — подробнее см. раздел «Авторизация и безопасность данных».
Контекст дашборда — context
Объект context содержит состояние дашборда на момент выполнения скрипта и доступен только для чтения:
context.filters # активные фильтры дашборда
context.measures # меры виджета
context.drill_enabled # включён ли drill-down
context.drill_path # шаги drill-down
context.take # ограничение на число строк
context.dashboard_id, context.widget_id, context.workspace_id, context.dataset_id, context.user_idРабота с фильтрами:
for f in context.filters: ... # перебрать фильтры
context.filters.all() # список всех фильтров
context.filters.get("Регион") # значение фильтра по колонке (или None)
context.filters.get("Регион", []) # со значением по умолчаниюget(...) возвращает список выбранных значений (например, ["Москва", "СПб"]) для обычного фильтра или {"start": ..., "end": ...} для диапазона дат или чисел.
По умолчанию client.query применяет все фильтры дашборда. Это поведение можно изменить:
client.query(..., filters=[]) # без фильтров
client.query(..., filters=context.filters.exclude("Год")) # все фильтры, кроме «Год»
client.query(..., filters=context.filters.only("Регион")) # только «Регион»Запросы к внешним системам (REST)
С помощью requests (или httpx) скрипт обращается к внешним сервисам — например, получает курсы валют, биржевые котировки или результат расчёта от стороннего сервиса:
import pandas as pd
import requests
response = requests.get("https://api.example.com/data", timeout=15)
df = pd.DataFrame(response.json())
return dfЗапрос можно построить на основе контекста: взять выбранные значения из context.filters и передать их во внешний сервис.
Платформа исходящие запросы не ограничивает — доступны любые внешние сервисы и HTTP-методы (GET, POST и т.д.). При этом фактическая доступность зависит от сетевой конфигурации стенда: в закрытом контуре внешний интернет может быть недоступен, а администратор может ограничить исходящий трафик (firewall, прокси). Отдельной сетевой изоляции и защиты от обращений к внутренним адресам (SSRF) нет — это учитывают при настройке безопасности платформы.
Рекомендации:
Параллельное выполнение запросов недоступно. Фоновые потоки и процессы заблокированы (
threading,multiprocessing), поэтому несколько REST-запросов выполняются последовательно. Учитывайте это вместе с общим лимитом времени выполнения скрипта.В каждом внешнем запросе следует задавать явный таймаут (параметр
timeout). Иначе ожидание ответа может упереться в общий лимит времени выполнения скрипта (см. раздел «Среда выполнения и ограничения»).Объём принимаемых данных следует ограничивать на стороне внешнего сервиса (фильтрация, агрегация) — в скрипт должны поступать уже подготовленные данные.
Авторизация во внешнем сервисе реализуется в коде скрипта — см. раздел «Авторизация и безопасность данных».
Режим обновления виджета
По умолчанию Python-виджет работает в автоматическом режиме: скрипт перезапускается сам при каждой смене фильтров дашборда и при выборе значения в других виджетах. Это удобно для лёгких вычислений по данным модели.
Если же скрипт делает ресурсоёмкий расчёт или обращается к внешнему сервису, постоянный перезапуск может быть нежелательным. В этом случае виджет можно перевести в ручной режим обновления.
Переключить режим можно в настройках виджета — тумблер «Ручное обновление».
В ручном режиме виджет не пересчитывается автоматически при смене фильтров. Скрипт выполняется только по нажатию кнопки обновления в правом верхнем углу виджета.
Так аналитик сам выбирает момент запуска — например, когда выставлены все нужные фильтры — и не нагружает внешний сервис на каждое изменение контекста.
Ручной режим уместен, когда:
скрипт выполняет дорогой запрос к внешнему сервису и запуск нужно контролировать вручную;
расчёт длительный или требует явного подтверждения (например, «Рассчитать прогноз»).
Кросс-фильтрация
Виджет на Python участвует в кросс-фильтрации наравне со стандартными:
входящая — виджет реагирует на фильтры и выбор значений в других виджетах;
исходящая — выбор значения в Python-виджете фильтрует другие виджеты.
Чтобы работала исходящая кросс-фильтрация, свяжите колонку результата с полем модели: переместите поле модели на соответствующую колонку результата. Так платформа определяет, по какому измерению публиковать фильтр.
Входящая фильтрация запускает пересчёт виджета только в автоматическом режиме обновления. В ручном режиме смена фильтров учитывается при следующем нажатии кнопки обновления.
Авторизация и безопасность данных
Доступ к данным модели
Для обращения к данным модели через client.query указывать токен не требуется: платформа выполняет запрос от имени текущего пользователя. Токен остаётся на стороне платформы и в скрипт не передаётся.
К данным применяются политики доступа модели:
RLS (Row-Level Security) — скрипт получает только те строки, к которым у пользователя есть доступ.
OLS (Object-Level Security) — таблицы, столбцы и меры, закрытые для пользователя политикой OLS, скрипту недоступны.
Доступ к внешним сервисам
Авторизация во внешнем сервисе реализуется средствами скрипта — например, заголовком Authorization или ключом API в параметрах запроса.
Параметры доступа (логины, ключи, токены) указываются непосредственно в тексте скрипта. Защищённое хранилище секретов (vault) в настоящий момент не поддерживается. Токен платформы для авторизации во внешних сервисах недоступен.
Среда выполнения и ограничения
Скрипты выполняются на Python 3.11 в изолированной среде. На выполнение скрипта действуют ресурсные ограничения:
Ресурс | Значение по умолчанию |
|---|---|
Оперативная память | 1800 МБ |
Процессорное время | 60 секунд |
Общее время выполнения (включая ожидание ответа внешних сервисов) | 180 секунд |
Размер файла | 100 МБ |
Значения лимитов настраиваются администратором платформы.
Ограничения по объёму данных
Единого лимита на число строк нет — он зависит от этапа:
Данные из модели (
client.query) — ограничены так же, как выгрузка в Excel (той же константой в конфигурации платформы). При превышении запрос к Formula Engine не выполнится. Поэтому агрегируйте в запросе черезmeasures, а не получайте сырые строки.Данные по REST — ограничения на число строк нет. Но всё полученное хранится в оперативной памяти скрипта (см. лимит памяти выше).
Результат скрипта (
return) → виджет — ограничен возможностями отрисовки виджета: на виджет передаётся не более ~10 000 строк, а многие визуализации «захлёбываются» заметно раньше (например, круговая диаграмма начинает подвисать уже на нескольких сотнях точек). Вreturnотдавайте агрегированный, готовый к отображению результат.
Память. Все данные, которые скрипт загрузил или создал (результаты client.query, ответы REST, промежуточные DataFrame), одновременно хранятся в оперативной памяти — в пределах 1.8 ГБ на один скрипт.
Как распределять нагрузку
агрегацию больших объёмов выполняет Formula Engine — через
client.queryс агрегатами вmeasures(или средствами DAX-виджета);Pandas применяйте для финальных преобразований и вычислений на уже агрегированных или ограниченных данных: ранжирование, скользящее среднее, расчёт долей, объединение с внешними данными и т.п.
Не загружайте в Python большие объёмы сырых данных из модели ради последующей агрегации в Pandas — агрегацию выполняет Formula Engine. Pandas работает с уже подготовленными данными.
Ошибки в скрипте
Если скрипт завершается с ошибкой, в редакторе (а на дашборде — в области виджета вместо визуализации) отображается traceback с пользовательскими кадрами — строками вашего скрипта. Служебные кадры платформы из него исключены, чтобы ошибку было удобнее искать.
Типовые ситуации отдаются отдельными понятными сообщениями: превышение лимитов памяти или времени, сбой инициализации среды выполнения, а также случай, когда скрипт не вернул результат.
Примеры
Динамика котировок выбранной компании (контекст + REST)
import pandas as pd
import requests
# 1) Фиксируем Тикер, который выбрал пользователь:
selection_df = client.query(rows=["'спр Компании'[Тикер]"]).to_pandas()
ticker = selection_df["Тикер"].tolist()[0] if len(selection_df) == 1 else "SBER"
# 2) Фиксируем период из фильтра по датам (ISO → YYYY-MM-DD); иначе дефолт
filters = context.filters.all()
period = next((f for f in filters if getattr(f, "start", None) or getattr(f, "end", None)), None)
date_from = (getattr(period, "start", None) or "2024-01-01")[:10]
date_to = (getattr(period, "end", None) or "2026-12-31")[:10]
# 3) REST к MOEX ISS
url = f"https://iss.moex.com/iss/history/engines/stock/markets/shares/boards/TQBR/securities/{ticker}.json"
resp = requests.get(url, params={"from": date_from, "till": date_to,
"iss.meta": "off", "history.columns": "TRADEDATE,CLOSE"}, timeout=15)
data = resp.json()["history"]["data"]
return pd.DataFrame(data, columns=["Дата", "Цена"])Структура портфеля по секторам (агрегация + REST)
import pandas as pd
import requests
from datetime import date, timedelta
# Виджет «Структура портфеля» (кольцевая диаграмма по секторам).
# Стоимость вложения в компанию = остаток её акций × цена акции на сегодня;
# затем стоимость суммируется по секторам — получаем доли секторов в портфеле.
# 1. Остаток акций по каждой компании = сколько купили минус сколько продали
positions = client.query(
rows=["'спр Компании'[Тикер]", "'спр Компании'[Сектор]"],
measures={"'Акции'[Покупка]": "Sum", "'Акции'[Продажа]": "Sum"},
).to_pandas()
positions["Остаток, шт"] = positions["Покупка"].fillna(0) - positions["Продажа"].fillna(0)
positions = positions[positions["Остаток, шт"] > 0] # только бумаги, что сейчас в портфеле
# 2. Цена каждой акции НА СЕГОДНЯ (цена закрытия за последний торговый день)
today = date.today()
window_from = (today - timedelta(days=10)).isoformat() # запас на выходные/праздники
def price_today(ticker):
url = f"https://iss.moex.com/iss/history/engines/stock/markets/shares/boards/TQBR/securities/{ticker}.json"
rows = requests.get(url, params={"from": window_from, "till": today.isoformat(),
"iss.meta": "off", "history.columns": "TRADEDATE,CLOSE"},
timeout=15).json()["history"]["data"]
closes = [close for _, close in rows if close is not None]
return closes[-1] if closes else None
positions["Цена, ₽"] = positions["Тикер"].map(price_today)
# 3. Стоимость пакета каждой компании = остаток × цена на сегодня
positions["Стоимость, ₽"] = positions["Остаток, шт"] * positions["Цена, ₽"]
# 4. Структура портфеля по секторам (данные для кольцевой диаграммы)
structure = positions.groupby("Сектор", as_index=False)["Стоимость, ₽"].sum()
structure["Стоимость, ₽"] = structure["Стоимость, ₽"].round(2)
return structureЧасто задаваемые вопросы
Сохраняются ли полученные данные в хранилище (DWH)?
Нет. Результат скрипта используется только для отрисовки виджета: он не записывается в хранилище данных и не попадает в модель. При каждом обновлении виджета скрипт выполняется заново.
Можно ли использовать полученные данные в других виджетах?
Нет. Скрипт привязан к своему виджету, и его результат недоступен другим виджетам. Между виджетами передаётся только фильтр (кросс-фильтрация), но не сами данные.
Установка сторонних библиотек (для администратора платформы)
Дополнительные пакеты подкладываются на хост в отдельную папку — без пересборки образа и без обращения к PyPI в момент выполнения (это работает и в закрытых контурах). Папка монтируется в контейнер только для чтения и прописана в PYTHONPATH, поэтому скрипты видят пакеты обычным import.
Один раз соберите пакеты со всеми зависимостями под тем же базовым образом, что и python-service:
mkdir build docker run --rm -v "$PWD/build:/build" <базовый-образ-python-service> \ pip install --target=/build prophet statsmodelsПеренесите содержимое в папку
customlibsхоста (${PERSISTENT_STORAGE_FOLDER}/python-service/customlibs):sudo cp -ra build/* <PERSISTENT_STORAGE_FOLDER>/python-service/customlibs/ sudo chmod -R a+rX <PERSISTENT_STORAGE_FOLDER>/python-service/customlibs
В контейнере папка доступна как /opt/customlibs. Перезапуск не требуется — новые пакеты увидит следующий запуск виджета.
Смотрите также
На этой странице
- 1 Включение Python-режима
- 2 Порядок настройки виджета
- 3 Возврат результата
- 4 Доступные библиотеки
- 5 Получение данных из модели — client.query
- 6 Контекст дашборда — context
- 7 Запросы к внешним системам (REST)
- 8 Режим обновления виджета
- 9 Кросс-фильтрация
- 10 Авторизация и безопасность данных
- 11 Среда выполнения и ограничения
- 12 Ошибки в скрипте
- 13 Примеры
- 14 Часто задаваемые вопросы
- 15 Установка сторонних библиотек (для администратора платформы)
Время чтения: 5 мин.
Нужна дополнительная помощь?