{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Yuriy Gavrilov: posts tagged Python",
    "_rss_description": "Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov",
    "_rss_language": "en",
    "_itunes_email": "yvgavrilov@gmail.com",
    "_itunes_categories_xml": "",
    "_itunes_image": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic-square@2x.jpg?1643451008",
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/gavrilov.info\/tags\/python\/",
    "feed_url": "https:\/\/gavrilov.info\/tags\/python\/json\/",
    "icon": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008",
    "authors": [
        {
            "name": "Yuriy Gavrilov - B[u]g - for charity.gavrilov.eth",
            "url": "https:\/\/gavrilov.info\/",
            "avatar": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008"
        }
    ],
    "items": [
        {
            "id": "345",
            "url": "https:\/\/gavrilov.info\/all\/upravlenie-dostupom-v-dashbordah-s-marimo-i-opa-prostoe-reshenie\/",
            "title": "Управление доступом в дашбордах с Marimo и OPA: простое решение для сложных политик",
            "content_html": "<p>В современном мире данных всё чаще требуется не только предоставлять пользователям интерактивные инструменты для анализа, но и жёстко контролировать, кто и к каким данным может обращаться. Классический подход — встраивать логику доступа прямо в код приложения — быстро становится негибким и трудно поддерживаемым. На помощь приходят специализированные решения: <b>Marimo<\/b> для построения дашбордов и <b>OPA<\/b> (Open Policy Agent) для централизованного управления политиками доступа.<\/p>\n<p>В этой статье мы разберём, как написать простое приложение на Marimo, которое проверяет права пользователя через OPA, а затем обсудим, как эта связка может быть расширена на полноценный дашборд, работающий с Trino и управляющий доступом к данным на разных уровнях.<\/p>\n<hr \/>\n<h3>Что такое Marimo и OPA?<\/h3>\n<p><b>Marimo<\/b> (<a href=\"https:\/\/github.com\/marimo-team\/marimo)\">https:\/\/github.com\/marimo-team\/marimo)<\/a> — это современный интерактивный блокнот для Python, который сочетает в себе удобство Jupyter с реактивностью и возможностью превращать блокнот в веб-приложение. В отличие от классических блокнотов, Marimo автоматически пересчитывает ячейки при изменении входных данных, что делает его идеальным для создания дашбордов и инструментов анализа.<\/p>\n<p><b>OPA<\/b> (Open Policy Agent: <a href=\"https:\/\/www.openpolicyagent.org)\">https:\/\/www.openpolicyagent.org)<\/a> — это универсальный движок политик с открытым исходным кодом. Он позволяет описывать правила доступа на языке Rego (декларативном, похожем на JSON) и принимать решения на основе входных данных (input). OPA работает как отдельный сервис, принимающий запросы и возвращающий разрешено или запрещено действие. Такой подход отделяет политики от кода приложения, упрощая их изменение, аудит и повторное использование.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-08-28-v-00.13.05.png\" width=\"1184\" height=\"1682\" alt=\"\" \/>\n<\/div>\n<hr \/>\n<h3>Простой пример: проверка доступа в Marimo<\/h3>\n<p>Представьте, что у нас есть дашборд, в котором разные пользователи могут видеть разные разделы. Вместо того чтобы хардкодить условия в Python, мы выносим логику в OPA.<\/p>\n<p>Ниже приведён фрагмент кода на Marimo, который реализует минимальный интерфейс для проверки доступа:<\/p>\n<p>Запускаем OPA<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">podman run -d --name opa-server -p 8181:8181 openpolicyagent\/opa \\                \n  run --server --addr=0.0.0.0:8181 --log-level debug<\/code><\/pre><p>Создаем политику Rego<\/p>\n<p>nano policy.rego<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">package app.authz\ndefault allow = false\nallow if {\n    input.user.role == &quot;admin&quot;\n}<\/code><\/pre><p>Отправляем политику в OPA:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">curl -X PUT --data-binary @policy.rego http:\/\/localhost:8181\/v1\/policies\/my_policy<\/code><\/pre><p>Далее запускаем новый блокнот Marimo<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">mkdir marimo-opa\ncd marimo-opa\nuv init \nuv add marimo requests\nuv run marimo edit app.py<\/code><\/pre><p>а теперь само приложение Marimo<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">import marimo as mo\nimport requests\n\n# Элементы управления\nuser_role = mo.ui.dropdown([&quot;user&quot;, &quot;admin&quot;], value=&quot;user&quot;, label=&quot;Роль пользователя:&quot;)\nresource = mo.ui.text(value=&quot;dashboard&quot;, label=&quot;Ресурс:&quot;)\nsubmit = mo.ui.button(label=&quot;Проверить доступ&quot;)\n\nmo.hstack([user_role, resource, submit])<\/code><\/pre><p>После нажатия кнопки код отправляет запрос к OPA:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">mo.stop(not submit.value, mo.md(&quot;*Нажмите кнопку для проверки прав*&quot;))\n\nopa_url = &quot;http:\/\/localhost:8181\/v1\/data\/app\/authz\/allow&quot;\npayload = {\n    &quot;input&quot;: {\n        &quot;user&quot;: {&quot;role&quot;: user_role.value},\n        &quot;resource&quot;: resource.value\n    }\n}\n\ntry:\n    response = requests.post(opa_url, json=payload).json()\n    is_allowed = response.get(&quot;result&quot;, False)\n    status = mo.md(&quot;**Доступ разрешен**&quot;) if is_allowed else mo.md(&quot;**Доступ запрещен**&quot;)\nexcept Exception as e:\n    status = mo.md(f&quot;**Ошибка подключения к ОРА:** {e}&quot;)\n\nstatus<\/code><\/pre><p>Что здесь происходит?<\/p>\n<ul>\n<li>Пользователь выбирает роль (user\/admin) и вводит название ресурса.<\/li>\n<li>При клике на кнопку формируется JSON с полем `input`, содержащим роль и ресурс.<\/li>\n<li>Этот JSON отправляется в OPA по эндпоинту, который возвращает решение (`result` — true\/false).<\/li>\n<li>На основе результата выводится сообщение о разрешении или запрете.<\/li>\n<\/ul>\n<p>Этот пример демонстрирует, как легко интегрировать OPA в интерфейс на Marimo. Все политики (например, `admin` может всё, а `user` — только определённые ресурсы) хранятся отдельно в OPA и могут быть изменены без переписывания кода приложения.<\/p>\n<hr \/>\n<h3>Расширяем сценарий: дашборд на Trino с двойным контролем доступа<\/h3>\n<p>Теперь представим более реальную задачу: мы строим дашборд для аналитики, который выполняет SQL-запросы к <b>Trino<\/b> — распределённому движку для работы с большими данными. В Trino доступ к таблицам, схемам и столбцам также можно регулировать через OPA. Таким образом, у нас возникает два уровня авторизации:<\/p>\n<ol start=\"1\">\n<li><b>Уровень приложения (Marimo)<\/b> — определяет, может ли пользователь вообще открыть определённый раздел дашборда или выполнить какое-то действие в интерфейсе.<\/li>\n<li><b>Уровень данных (Trino)<\/b> — контролирует, какие именно данные (таблицы, колонки, строки) пользователь может запрашивать через SQL.<\/li>\n<\/ol>\n<p>Эти два уровня могут обслуживаться <b>разными экземплярами OPA<\/b> или одним, но с разными пакетами политик. Например, в Marimo мы проверяем политику `app\/authz\/allow`, а в Trino — `trino\/authz\/allow`. Это даёт гибкость: политики для интерфейса и для данных могут управляться разными командами или обновляться независимо.<\/p>\n<p>В нашем приложении Marimo после успешной проверки прав на доступ к разделу мы можем формировать SQL-запрос и отправлять его в Trino. При этом Trino, в свою очередь, проверит, имеет ли пользователь право читать запрашиваемые таблицы, используя свой OPA-агент. Если хотя бы один из агентов вернёт отказ, пользователь не получит данные.<\/p>\n<p>Такая двухуровневая архитектура обеспечивает безопасность «в глубину» и позволяет тонко настраивать политики как на уровне интерфейса, так и на уровне сырых данных.<\/p>\n<hr \/>\n<h3>Управление политиками OPA: взгляд в будущее<\/h3>\n<p>Настройка и поддержка политик OPA, особенно когда речь идёт о десятках таблиц, ролей и атрибутов, может стать сложной задачей. Требуется не только писать правила на Rego, но и поддерживать актуальность данных о пользователях и ресурсах. Для упрощения этой работы существует проект <b>Moat<\/b> (Data Control Plane для Trino и OPA).<\/p>\n<p>Moat предоставляет: <a href=\"https:\/\/github.com\/moat-io\/moat\">https:\/\/github.com\/moat-io\/moat<\/a><\/p>\n<ul>\n<li>SCIM2.0-сервер для интеграции с провайдерами идентичности (Okta, EntraId и др.);<\/li>\n<li>ингестию атрибутов пользователей и групп из различных источников (SQL, LDAP);<\/li>\n<li>ингестию метаданных о таблицах и представлениях из каталогов данных;<\/li>\n<li>готовые политики Rego для типовых сценариев (RBAC, ABAC);<\/li>\n<li>OPA-совместимый Bundle API с кешированием для быстрого обновления политик.<\/li>\n<\/ul>\n<p>Moat сам не принимает решений в рантайме — он лишь поставляет OPA необходимую информацию (политики и данные). Это позволяет удобно управлять множеством кластеров Trino и эфемерных окружений, добавляя OPA-контейнер в каждый координатор и указывая ему на Moat как источник бандлов.<\/p>\n<p>Однако подробное рассмотрение Moat выходит за рамки этой статьи. Мы обязательно вернёмся к нему в отдельном материале, где разберём его архитектуру, настройку и примеры использования.<\/p>\n<hr \/>\n<h3>Заключение<\/h3>\n<p>Связка Marimo и OPA даёт разработчикам дашбордов мощный и гибкий инструмент для управления доступом. Вы можете отделить политики от кода, упростить их изменение и аудит, а также комбинировать несколько OPA-агентов для разных уровней — приложения и данных. В сочетании с Trino и такими инструментами, как Moat, эта экосистема становится полноценным решением для корпоративных аналитических платформ.<\/p>\n<p>Попробуйте предложенный пример в своём проекте — и вы убедитесь, как легко внедрить централизованное управление доступом, не усложняя код приложения. А о Moat мы расскажем в следующий раз!<\/p>\n",
            "date_published": "2026-08-28T00:14:17+03:00",
            "date_modified": "2026-08-28T00:16:01+03:00",
            "tags": [
                "Data",
                "Open Source",
                "Python",
                "Security"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-08-28-v-00.13.05.png",
            "_date_published_rfc2822": "Fri, 28 Aug 2026 00:14:17 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "345",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-08-28-v-00.13.05.png"
                ]
            }
        },
        {
            "id": "81",
            "url": "https:\/\/gavrilov.info\/all\/kak-sdelat-scatter-plot-i-poschitat-v-kakoy-strane-est-internet\/",
            "title": "Как сделать scatter plot и посчитать В какой стране есть Интернет стоимостью почти 800 за Гб",
            "content_html": "<p>Все вроде не сложно, но вот наименование точек оказалось самым сложным)<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">import pandas as pd\nimport matplotlib.pyplot as plt\ndf = pd.read_csv('https:\/\/a.gavrilov.info\/data\/python\/file.csv')\ndf.iloc[:, 6] = df.iloc[:, 6].str.replace('$','',regex=True)\ndf.iloc[:, 5] = df.iloc[:, 5].str.replace('$','',regex=True)\ndf.iloc[:, 5] = df.iloc[:, 5].astype(float)\ndf.iloc[:, 6] = df.iloc[:, 6].astype(float)\nfig, ax = plt.subplots(figsize=(10, 6))\nax.scatter(x = df[&quot;Cheapest 1GB for 30 days (USD)&quot;], y = df[&quot;Most expensive 1GB (USD)&quot;])\nfor idx, row in df.iterrows():\n ax.annotate(row['Name'], (row['Cheapest 1GB for 30 days (USD)'], row['Most expensive 1GB (USD)']))\nplt.show()<\/code><\/pre><p>Это одно из заданий, которые встречаются на курсах по питону.<\/p>\n<p>Файл данных тут: <a href=\"https:\/\/a.gavrilov.info\/data\/python\/file.csv,\">https:\/\/a.gavrilov.info\/data\/python\/file.csv,<\/a> это затраты на интернет в разных странах.<\/p>\n<p>А задание само такое: “Построим: диаграмму рассеяния по столбцам таблицы ‘Cheapest 1GB for 30 days (USD)’ и ‘Most Expensive 1GB (USD)’. В какой стране есть Интеренет стоимостью почти 800 за Гб, если верить таблице?”<\/p>\n",
            "date_published": "2023-11-12T23:10:06+03:00",
            "date_modified": "2023-11-12T23:09:20+03:00",
            "tags": [
                "Python"
            ],
            "_date_published_rfc2822": "Sun, 12 Nov 2023 23:10:06 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "81",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "25",
            "url": "https:\/\/gavrilov.info\/all\/risuyu-prostuyu-diagrammu-na-python\/",
            "title": "Рисую простую диаграмму на Python",
            "content_html": "<p>Вот собственно сам код:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">import pandas as pd\nimport altair as alt\n\ndf = pd.read_excel('Sample - Superstore.xls', index_col=0) \ndf2 = df.groupby('Region').sum()\ndf2 = df2.reset_index(level=0)\n\nbars = alt.Chart(df2).mark_bar().encode(\n   alt.X('Region:N', sort='-y'),\n   alt.Y('Sales:Q'),\n   color=alt.condition(  alt.datum.Region == 'West',  # If the year is 1810 this test returns True,\n   alt.value('orange'),     # which sets the bar orange.\n   alt.value('steelblue'))   \n)\n\ntext = bars.mark_text(\n    align='left',\n    baseline='middle',\n    dx=11  # Nudges text to right so it doesn't appear on top of the bar\n).encode(\n    text='mean(Sales):Q'\n)\n\n(bars + text).properties(height=300)<\/code><\/pre><div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/visualization.png\" width=\"209\" height=\"370\" alt=\"\" \/>\n<\/div>\n",
            "date_published": "2022-07-09T19:36:03+03:00",
            "date_modified": "2022-07-09T20:11:14+03:00",
            "tags": [
                "Data Visualization",
                "Python"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/visualization.png",
            "_date_published_rfc2822": "Sat, 09 Jul 2022 19:36:03 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "25",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/visualization.png"
                ]
            }
        }
    ],
    "_e2_version": 4171,
    "_e2_ua_string": "Aegea 11.4 (v4171e)"
}