Jupyter обычно ассоциируется с Python, анализом данных и машинным обучением. Пишем несколько строк, выполняем ячейку, смотрим результат, а в следующей продолжаем работать с уже полученными объектами. Если нужно — тут же строим график, проверяем другую гипотезу или меняем часть расчёта.
Мне давно хотелось получить похожий цикл работы и для 1С.
Именно интерактивную среду с состоянием: выполнил несколько строк, получил объект 1С, использовал его в следующей ячейке, вызвал типовой метод конфигурации, передал результат в Python, что-то проверил — и продолжил с того же места.
Так появился BSL Jupyter Runtime - проект с открытым исходным кодом.
В этой статье пройдём весь пользовательский сценарий. Начнём с классического "Привет, мир!", затем посмотрим, как между ячейками сохраняются переменные и методы, поработаем с настоящей базой ЗУП, передадим объекты 1С в Pandas.DataFrame, посмотрим, как перезагружать код общих модулей не перезапуская сеанс 1С. А в конце "поймаем" процесс проведения документа "Прием на работу" и увидим, как прямо в notebook мы можем выполнять ячейки меняющие состояние документа, прямо в процессе проведения.
Это первая статья цикла. Здесь будет обзор всех возможностей инструмента. В следующих статьях мы более подробно разберем возможности перезагрузки общих модулей на лету и возможности анализа и модификации состояния процесса, когда мы поймали точку останова.
Да будет волшебство!
Начнём с классического примера:
%%bsl
Сообщить("Привет, мир!");
В Jupyter такие конструкции называются cell magic. %%bsl — пожалуй, действительно волшебная ячейка: ниже можно писать обычный 1С код.
В итоге, прямо из Jupyter можно выполнять код 1С — причём не в абстрактном окружении, а в контексте нашей информационной базы, с её данными, объектами и модулями. Более того, после выполнения состояние не исчезает — переменные, объекты и объявленные методы можно использовать дальше.
Cледущая BSL-ячейка продолжает работать с тем же контекстом.
%%bsl
ПроцентПовышения = 10;
А в следующей ячейке просто используем уже заданное значение:
%%bsl
Сообщить(ПроцентПовышения);
Получим `10`.
То есть BSL-ячейки не являются независимыми одноразовыми вызовами: notebook хранит состояние между выполнениями.
Состояние может содержать не только значения. Объявим функцию:
%%bsl
Функция УвеличитьНаПроцент(Значение)
Возврат Значение * (1 + ПроцентПовышения / 100);
КонецФункции
Первый вызов УвеличитьНаПроцент(100000) вернёт 110000.
Теперь в другой ячейке изменим ПроцентПовышения:
%%bsl
ПроцентПовышения = 20;
И снова вызовем ту же функцию:
%%bsl
Сообщить(УвеличитьНаПроцент(100000));
Получим: 120000

Саму функцию мы при этом не переопределяли — она осталась в общем контексте notebook и использовала актуальное значение переменной.
В обычном сценарии для подобных экспериментов часто приходится менять код внешней обработки или модуля, сохранять изменения и заново воспроизводить нужный сценарий. Здесь достаточно изменить одну ячейку или добавить новую — уже созданные объекты, объявленные методы и остальное состояние остаются на месте.
Можно один раз подготовить окружение эксперимента, задать нужные параметры, получить объекты 1С, а дальше шаг за шагом менять отдельные значения и проверять, как это влияет на результат.
На этом простом примере уже видна базовая модель runtime: notebook становится журналом одного интерактивного эксперимента, а не набором независимых скриптов.
Внутри ЗУП
Пока всё это похоже на удобную интерактивную консоль BSL. Поэтому следующий шаг важнее: из того же контекста можно обращаться к обычному коду конфигурации.
Возьмём демо-базу ЗУП и получим кадровые данные через публичный API общего модуля `КадровыйУчет`:
%%bsl
ДатаФОТ = Дата(2021, 8, 1);
ПараметрыСотрудников = КадровыйУчет.ПараметрыПолученияСотрудниковОрганизацийПоСпискуФизическихЛиц();
ПараметрыСотрудников.НачалоПериода = ДатаФОТ;
ПараметрыСотрудников.ОкончаниеПериода = ДатаФОТ;
ПараметрыСотрудников.РаботникиПоТрудовымДоговорам = Истина;
ПараметрыСотрудников.КадровыеДанные = "Организация,Подразделение,Должность,ФОТ";
КадровыеДанные = КадровыйУчет.СотрудникиОрганизации(
Истина, ПараметрыСотрудников);
Это не специальный Python API для ЗУП и не копия данных в стороннем процессе. Выполняется обычный код типовой конфигурации ЗУП, а полученная таблица остаётся жить в сеансе 1С.
Из 1С в Python
Пока полученные нами `КадровыеДанные` живут в сеансе 1С. Но при необходимости данные можно перенести в Python и использовать такие мощные инструменты, как Pandas.DataFrame и Matplotlib для обработки и визуализации данных.
df = КадровыеДанные.to_df(refs="presentation")
display(df.head(10))

Для ссылочных значений можно выбирать человекочитаемое представление, UUID или оба варианта. Но главное здесь не конкретный параметр `to_df()`, а граница ответственности: бизнес-логика и получение данных остаются в 1С, а Python используется там, где он удобнее для исследования результата.
Дальше это обычный pandas
После материализации нет отдельного «режима 1С». Это обычный DataFrame. Например, можно сгруппировать ФОТ по подразделениям и построить диаграмму:

Сам график ничего революционного не представляет. Интересно, что для него не пришлось выгружать промежуточный CSV, писать HTTP-сервис или воспроизводить расчёт ЗУП на Python. Сначала отработала конфигурация, а затем мы продолжили исследование уже в Python.
Меняем код общего модуля "на лету"
До сих пор мы либо писали новый код в notebook, либо вызывали уже существующий код ЗУП. Но во время исследования часто хочется изменить существующий метод и тут же посмотреть результат. Само изменение может занимать одну строку — гораздо больше времени иногда занимает повторное воспроизведение состояния, в котором эту строку нужно проверить.
Для примера возьмём:

Мы хотим, чтобы этот метод возвращал ещё одну колонку — ФОТСоВзносами.
Казалось бы, тут без перезапуска сеанса не обойтись.
Но теперь достаточно в локальной копии исходников добавить в запрос вычисляемое поле — ФОТ, умноженный на 1,3. Изменяется только локальный файл:

Затем загружаем изменённую реализацию в текущий экспериментальный runtime. Причём даже для модуля объёмом около 20 тысяч строк это занимает всего несколько секунд:
RELOAD_MODULE_PATH = r"CommonModules\ПлановыеНачисленияСотрудников\Ext\Module.bsl"
runtime.load_worker_module(RELOAD_MODULE_PATH)
Повторяем тот же BSL-вызов и видим новое поле в результате:

Здесь важно само свойство среды: изменили локальный код, загрузили новую реализацию и продолжили эксперимент в том же сеансе и с тем же состоянием notebook.
Как это устроено внутри, что происходит с зависимостями и где проходят границы hot reload — разберём во второй статье.
В частности, посмотрим, как ведут себя зависимости между перегруженными модулями: например, почему ранее загруженный модуль A после перегрузки вызываемого им модуля B начинает работать уже с новой реализацией B — без повторной загрузки самого A.
Остановка внутри выполняющегося кода
Hot reload отвечает на вопрос «что будет, если изменить код?».
А теперь представим, что нам нужно отладить код проведения документа. Здесь нам нужны данные, которые существуют только внутри выполняющегося расчёта. Метод завершился — и внутренний контекст уже потерян, остаётся только результат.
Обычно такие места исследуют через отладчик. Он позволяет посмотреть стек, вычислить выражение и даже изменить значения переменных.
Но иногда этого мало. Хочется, не выходя из остановленного проведения, выполнить свой 1С-код: вызвать нужную процедуру, изменить состояние расчёта, пересоздать временную таблицу, проверить ещё одну гипотезу — и только после этого продолжить тот же вызов.
Именно это попробуем сделать из notebook.
Для примера возьмём существующий документ `Прием на работу` и остановим выполнение в типовом `РасчетЗарплатыРасширенный.СформироватьДвиженияПлановыхНачислений` на строке 768.

Механику постановки точки остановки здесь опустим — она будет подробно разобрана в отдельной статье.
Далее из ячейки запустим код проведения документа:
%%bsl
Прием.Записать(РежимЗаписиДокумента.Проведение);
Вызов не возвращается обычным результатом: runtime останавливает выполнение внутри типового кода и позволяет посмотреть стек текущего вызова.

Ключевой момент: `Прием.Записать()` ещё не завершился. Тот самый вызов проведения, который мы запустили из notebook, всё ещё активен.
Пока вызов остановлен, с ним можно работать
На остановленном фрейме, через переменную КонтекстОтладки, доступны данные текущего вызова. В нашем примере из структуры проведения можно прочитать показатель оклада — 45 000. Это уже не запрос к записанным регистрам и не отдельно воспроизведённый расчёт: мы читаем состояние незавершённого вызова.

В начале статьи функция `УвеличитьНаПроцент` была учебным примером. Теперь используем ту же идею для данных остановленного фрейма: меняем 45 000 на 49 500, оставаясь внутри того же незавершённого выполнения.
После изменения продолжаем остановленный вызов через `runtime.resume_capture()`.

И это главное отличие от повторного запуска эксперимента: после изменения мы не проводим документ заново. Продолжается именно тот вызов, который был остановлен.
После завершения читаем данные уже обычным способом и видим новое значение:

В приложенном notebook есть дополнительные проверки движений и плановых данных.
Именно работа с остановленным фреймом будет темой третьей статьи: стек, контекст незавершённого вычисления, выполнение 1С-ячеек во время паузы, изменение данных, пересоздание временных таблиц, перезагрузка общих модулей и ограничения такого режима.
Не только ЗУП: тот же runtime, другой стиль работы
Основной пример статьи получился сильно завязан на ЗУП, поэтому к статье приложен второй notebook на УТ 11.6.1.61. На нём хорошо видно, что runtime не навязывает конфигурации какой-то новый способ доступа к данным.
В ЗУП многое удобно получать через публичные методы общих модулей. В УТ типичный исследовательский сценарий может выглядеть иначе: напрямую написать запрос к регистру или виртуальной таблице, а затем материализовать результат.
Например, обороты регистра ВыручкаИСебестоимостьПродаж можно получить обычным запросом внутри BSL-ячейки и тут же забрать результат в Python:

В ЗУП мы в основном работали через прикладной API конфигурации. В УТ — напрямую строим нужный срез данных запросом.
Для runtime разницы нет: внутри %%bsl мы просто пишем обычный код 1С и используем те возможности платформы и конфигурации, которые удобны в конкретной задаче.
BSL в notebook — не текстовое поле
Если BSL-ячейка умеет только отправлять текст на выполнение, писать в ней реальный код довольно быстро станет неудобно. Поэтому в VS Code notebook связан с локальной выгрузкой конфигурации, по которой редактор понимает её объекты, методы и типы.
Для %%bsl доступны подсветка синтаксиса, автодополнение, подсказки по свойствам и методам, hover, сигнатуры и переход к определениям.
Например, редактор прямо в notebook показывает документацию публичного метода:

И понимает свойства возвращаемой структуры, включая типы и описания:

То есть notebook остаётся частью обычного VS Code workflow, а не превращается в отдельную «слепую» консоль.
И здесь хочется отдельно поблагодарить разработчиков 1c-syntax. Без уже существующей экосистемы Language 1C (BSL) и BSL Language Server делать полноценный редактор BSL внутри notebook пришлось бы практически с нуля.
Вместо этого удалось опереться на огромную работу, которая уже проделана сообществом: описание языка и типов, документацию методов, навигацию по исходникам, автодополнение и другие возможности редактора.
Задача 1C BSL Notebooks здесь была в другом — подружить всё это с BSL-ячейками и сделать так, чтобы код внутри notebook ощущался не чужеродной вставкой, а нормальной частью рабочего окружения 1С-разработчика.
Что в итоге получилось
Если посмотреть на весь путь целиком, мы начали с Сообщить("Привет, мир!"), а закончили изменением состояния внутри ещё не завершившегося типового проведения.

Каждый отдельный элемент этого сценария сам по себе понятен разработчику: код можно выполнить, данные — получить и выгрузить, выполнение — остановить в отладчике. Но в BSL Jupyter Runtime всё это становится частями одного интерактивного эксперимента с общим состоянием.
Можно один раз подготовить нужный контекст, получить объекты 1С, вызвать типовой код, передать результат в Python, изменить реализацию общего модуля, остановиться внутри выполняющегося расчёта, исследовать его состояние — и продолжить работу, не начиная весь эксперимент заново.
Особенно хорошо такой подход подходит для «дорогих» исследований: сложных расчётов ЗУП и НДФЛ, длинных цепочек запросов и временных таблиц, разбора типового кода, где каждый новый прогон требует заметной подготовки, а также случаев, когда результат удобно дальше исследовать средствами Python.
Что нужно для запуска
Важно: все эксперименты в статье выполняются на отдельной копии информационной базы. «Живая база» в заголовке означает настоящий runtime 1С и реальные объекты конфигурации, а не production-ИБ.
Для запуска примеров понадобятся:
- Windows с установленной платформой 1С;
- Python 3.12+;
- VS Code;
- отдельная копия информационной базы.
Расширения VS Code. В VS Code установите:
- Python — поддержка Python-окружения (обязательно);
- Jupyter — работа с notebook прямо внутри VS Code (обязательно);
- Pylance — рекомендуется для автодополнения и анализа Python-кода;
- Language 1C (BSL) — language services для BSL;
- 1C BSL Notebooks — поддержка "%%bsl"-ячеек внутри notebook. (устанавливаем через команду Extensions: Install from VSIX.)
Первые три отвечают за Python- и notebook-часть. Связка Language 1C (BSL) и 1C BSL Notebooks нужна для полноценной работы с кодом 1С: подсветки, автодополнения, hover, сигнатур и переходов к определениям.
Runtime
Python-пакет устанавливается из командной строки:
pip install onec-interactive-jupyter
После этого notebook может запускать BSL-код в сеансе 1С.
Исходники конфигурации
Чтобы редактор понимал объекты и методы именно вашей конфигурации, её исходники должны быть открыты в текущем workspace VS Code.
Поддерживаются оба распространённых формата:
- выгрузка конфигурации в файлы из Конфигуратора;
- проект конфигурации в формате EDT.
По этим исходникам редактор получает информацию об объектах конфигурации, общих модулях, методах и типах.
Запуск сессии
Интерактивная сессия с информационной базой запускается из обычной Python-ячейки:
from IPython.display import display
from onec_runtime.config import RuntimeConfig
from onec_runtime.session import ExtensionMode, RuntimeSessionConfig
from onec_runtime_jupyter import InteractiveRuntimeSession
PLATFORM_BIN = r'C:\Program Files\1cv8\8.5.1.1529\bin'
CONNECTION_STRING = r'File="C:\path\to\base";'
SOURCE_ROOT = r'C:\path\to\sources'
runtime = InteractiveRuntimeSession.start(
RuntimeSessionConfig(
runtime=RuntimeConfig(
platform_bin=PLATFORM_BIN,
connection_string=CONNECTION_STRING,
username="Имя пользователя ИБ",
),
source_root=SOURCE_ROOT,
extension_mode=ExtensionMode.AUTO
)
)
Материалы к статье
К статье приложены два исполняемых notebook.
-
ЗУП — основной обзорный сценарий: persistent state, типовой API, pandas, hot reload и работа с остановленным фреймом.
-
УТ — независимый сценарий: язык запросов, регистр `ВыручкаИСебестоимостьПродаж`, pandas и дополнительные эксперименты с типовой логикой.
Репозиторий: pulh1/bsl-jupyter-runtime
Что дальше
В этой обзорной статье мы только попробовали две самые глубокие возможности runtime. В следующих частях разберём их уже по устройству, а не только по внешнему эффекту.
Сначала — hot reload. Посмотрим, как из локально изменённого метода строится временная реализация, как разрешаются его зависимости на код конфигурации, что происходит с контекстом исполнения и где проходят ограничения такого подхода.
Затем — работа с остановленным фреймом. Разберём, как остановиться внутри реального вызова, посмотреть стек и текущий контекст, выполнять BSL-ячеки во время паузы, читать и менять данные незавершённого вычисления и после этого продолжить тот же вызов с места остановки.
То есть в первой статье мы собрали весь пользовательский workflow, а дальше будем разбирать два механизма, которые делают этот workflow действительно необычным для разработки на 1С.
Вступайте в нашу телеграмм-группу Инфостарт