<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Yuriy Gavrilov: posts tagged Python</title>
<link>https://gavrilov.info/tags/python/</link>
<description>Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov</description>
<author></author>
<language>en</language>
<generator>Aegea 11.4 (v4171e)</generator>

<itunes:owner>
<itunes:name></itunes:name>
<itunes:email>yvgavrilov@gmail.com</itunes:email>
</itunes:owner>
<itunes:subtitle>Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov</itunes:subtitle>
<itunes:image href="https://gavrilov.info/pictures/userpic/userpic-square@2x.jpg?1643451008" />
<itunes:explicit>no</itunes:explicit>

<item>
<title>Управление доступом в дашбордах с Marimo и OPA: простое решение для сложных политик</title>
<guid isPermaLink="false">345</guid>
<link>https://gavrilov.info/all/upravlenie-dostupom-v-dashbordah-s-marimo-i-opa-prostoe-reshenie/</link>
<pubDate>Fri, 28 Aug 2026 00:14:17 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/upravlenie-dostupom-v-dashbordah-s-marimo-i-opa-prostoe-reshenie/</comments>
<description>
&lt;p&gt;В современном мире данных всё чаще требуется не только предоставлять пользователям интерактивные инструменты для анализа, но и жёстко контролировать, кто и к каким данным может обращаться. Классический подход — встраивать логику доступа прямо в код приложения — быстро становится негибким и трудно поддерживаемым. На помощь приходят специализированные решения: &lt;b&gt;Marimo&lt;/b&gt; для построения дашбордов и &lt;b&gt;OPA&lt;/b&gt; (Open Policy Agent) для централизованного управления политиками доступа.&lt;/p&gt;
&lt;p&gt;В этой статье мы разберём, как написать простое приложение на Marimo, которое проверяет права пользователя через OPA, а затем обсудим, как эта связка может быть расширена на полноценный дашборд, работающий с Trino и управляющий доступом к данным на разных уровнях.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Что такое Marimo и OPA?&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Marimo&lt;/b&gt; (&lt;a href="https://github.com/marimo-team/marimo)"&gt;https://github.com/marimo-team/marimo)&lt;/a&gt; — это современный интерактивный блокнот для Python, который сочетает в себе удобство Jupyter с реактивностью и возможностью превращать блокнот в веб-приложение. В отличие от классических блокнотов, Marimo автоматически пересчитывает ячейки при изменении входных данных, что делает его идеальным для создания дашбордов и инструментов анализа.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;OPA&lt;/b&gt; (Open Policy Agent: &lt;a href="https://www.openpolicyagent.org)"&gt;https://www.openpolicyagent.org)&lt;/a&gt; — это универсальный движок политик с открытым исходным кодом. Он позволяет описывать правила доступа на языке Rego (декларативном, похожем на JSON) и принимать решения на основе входных данных (input). OPA работает как отдельный сервис, принимающий запросы и возвращающий разрешено или запрещено действие. Такой подход отделяет политики от кода приложения, упрощая их изменение, аудит и повторное использование.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-08-28-v-00.13.05.png" width="1184" height="1682" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Простой пример: проверка доступа в Marimo&lt;/h3&gt;
&lt;p&gt;Представьте, что у нас есть дашборд, в котором разные пользователи могут видеть разные разделы. Вместо того чтобы хардкодить условия в Python, мы выносим логику в OPA.&lt;/p&gt;
&lt;p&gt;Ниже приведён фрагмент кода на Marimo, который реализует минимальный интерфейс для проверки доступа:&lt;/p&gt;
&lt;p&gt;Запускаем OPA&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;podman run -d --name opa-server -p 8181:8181 openpolicyagent/opa \                
  run --server --addr=0.0.0.0:8181 --log-level debug&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Создаем политику Rego&lt;/p&gt;
&lt;p&gt;nano policy.rego&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;package app.authz
default allow = false
allow if {
    input.user.role == &amp;quot;admin&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Отправляем политику в OPA:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;curl -X PUT --data-binary @policy.rego http://localhost:8181/v1/policies/my_policy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Далее запускаем новый блокнот Marimo&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir marimo-opa
cd marimo-opa
uv init 
uv add marimo requests
uv run marimo edit app.py&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;а теперь само приложение Marimo&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import marimo as mo
import requests

# Элементы управления
user_role = mo.ui.dropdown([&amp;quot;user&amp;quot;, &amp;quot;admin&amp;quot;], value=&amp;quot;user&amp;quot;, label=&amp;quot;Роль пользователя:&amp;quot;)
resource = mo.ui.text(value=&amp;quot;dashboard&amp;quot;, label=&amp;quot;Ресурс:&amp;quot;)
submit = mo.ui.button(label=&amp;quot;Проверить доступ&amp;quot;)

mo.hstack([user_role, resource, submit])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После нажатия кнопки код отправляет запрос к OPA:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mo.stop(not submit.value, mo.md(&amp;quot;*Нажмите кнопку для проверки прав*&amp;quot;))

opa_url = &amp;quot;http://localhost:8181/v1/data/app/authz/allow&amp;quot;
payload = {
    &amp;quot;input&amp;quot;: {
        &amp;quot;user&amp;quot;: {&amp;quot;role&amp;quot;: user_role.value},
        &amp;quot;resource&amp;quot;: resource.value
    }
}

try:
    response = requests.post(opa_url, json=payload).json()
    is_allowed = response.get(&amp;quot;result&amp;quot;, False)
    status = mo.md(&amp;quot;**Доступ разрешен**&amp;quot;) if is_allowed else mo.md(&amp;quot;**Доступ запрещен**&amp;quot;)
except Exception as e:
    status = mo.md(f&amp;quot;**Ошибка подключения к ОРА:** {e}&amp;quot;)

status&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Что здесь происходит?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Пользователь выбирает роль (user/admin) и вводит название ресурса.&lt;/li&gt;
&lt;li&gt;При клике на кнопку формируется JSON с полем `input`, содержащим роль и ресурс.&lt;/li&gt;
&lt;li&gt;Этот JSON отправляется в OPA по эндпоинту, который возвращает решение (`result` — true/false).&lt;/li&gt;
&lt;li&gt;На основе результата выводится сообщение о разрешении или запрете.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Этот пример демонстрирует, как легко интегрировать OPA в интерфейс на Marimo. Все политики (например, `admin` может всё, а `user` — только определённые ресурсы) хранятся отдельно в OPA и могут быть изменены без переписывания кода приложения.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Расширяем сценарий: дашборд на Trino с двойным контролем доступа&lt;/h3&gt;
&lt;p&gt;Теперь представим более реальную задачу: мы строим дашборд для аналитики, который выполняет SQL-запросы к &lt;b&gt;Trino&lt;/b&gt; — распределённому движку для работы с большими данными. В Trino доступ к таблицам, схемам и столбцам также можно регулировать через OPA. Таким образом, у нас возникает два уровня авторизации:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Уровень приложения (Marimo)&lt;/b&gt; — определяет, может ли пользователь вообще открыть определённый раздел дашборда или выполнить какое-то действие в интерфейсе.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Уровень данных (Trino)&lt;/b&gt; — контролирует, какие именно данные (таблицы, колонки, строки) пользователь может запрашивать через SQL.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Эти два уровня могут обслуживаться &lt;b&gt;разными экземплярами OPA&lt;/b&gt; или одним, но с разными пакетами политик. Например, в Marimo мы проверяем политику `app/authz/allow`, а в Trino — `trino/authz/allow`. Это даёт гибкость: политики для интерфейса и для данных могут управляться разными командами или обновляться независимо.&lt;/p&gt;
&lt;p&gt;В нашем приложении Marimo после успешной проверки прав на доступ к разделу мы можем формировать SQL-запрос и отправлять его в Trino. При этом Trino, в свою очередь, проверит, имеет ли пользователь право читать запрашиваемые таблицы, используя свой OPA-агент. Если хотя бы один из агентов вернёт отказ, пользователь не получит данные.&lt;/p&gt;
&lt;p&gt;Такая двухуровневая архитектура обеспечивает безопасность «в глубину» и позволяет тонко настраивать политики как на уровне интерфейса, так и на уровне сырых данных.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Управление политиками OPA: взгляд в будущее&lt;/h3&gt;
&lt;p&gt;Настройка и поддержка политик OPA, особенно когда речь идёт о десятках таблиц, ролей и атрибутов, может стать сложной задачей. Требуется не только писать правила на Rego, но и поддерживать актуальность данных о пользователях и ресурсах. Для упрощения этой работы существует проект &lt;b&gt;Moat&lt;/b&gt; (Data Control Plane для Trino и OPA).&lt;/p&gt;
&lt;p&gt;Moat предоставляет: &lt;a href="https://github.com/moat-io/moat"&gt;https://github.com/moat-io/moat&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SCIM2.0-сервер для интеграции с провайдерами идентичности (Okta, EntraId и др.);&lt;/li&gt;
&lt;li&gt;ингестию атрибутов пользователей и групп из различных источников (SQL, LDAP);&lt;/li&gt;
&lt;li&gt;ингестию метаданных о таблицах и представлениях из каталогов данных;&lt;/li&gt;
&lt;li&gt;готовые политики Rego для типовых сценариев (RBAC, ABAC);&lt;/li&gt;
&lt;li&gt;OPA-совместимый Bundle API с кешированием для быстрого обновления политик.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Moat сам не принимает решений в рантайме — он лишь поставляет OPA необходимую информацию (политики и данные). Это позволяет удобно управлять множеством кластеров Trino и эфемерных окружений, добавляя OPA-контейнер в каждый координатор и указывая ему на Moat как источник бандлов.&lt;/p&gt;
&lt;p&gt;Однако подробное рассмотрение Moat выходит за рамки этой статьи. Мы обязательно вернёмся к нему в отдельном материале, где разберём его архитектуру, настройку и примеры использования.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Связка Marimo и OPA даёт разработчикам дашбордов мощный и гибкий инструмент для управления доступом. Вы можете отделить политики от кода, упростить их изменение и аудит, а также комбинировать несколько OPA-агентов для разных уровней — приложения и данных. В сочетании с Trino и такими инструментами, как Moat, эта экосистема становится полноценным решением для корпоративных аналитических платформ.&lt;/p&gt;
&lt;p&gt;Попробуйте предложенный пример в своём проекте — и вы убедитесь, как легко внедрить централизованное управление доступом, не усложняя код приложения. А о Moat мы расскажем в следующий раз!&lt;/p&gt;
</description>
</item>

<item>
<title>Как сделать scatter plot и посчитать В какой стране есть Интернет стоимостью почти 800 за Гб</title>
<guid isPermaLink="false">81</guid>
<link>https://gavrilov.info/all/kak-sdelat-scatter-plot-i-poschitat-v-kakoy-strane-est-internet/</link>
<pubDate>Sun, 12 Nov 2023 23:10:06 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/kak-sdelat-scatter-plot-i-poschitat-v-kakoy-strane-est-internet/</comments>
<description>
&lt;p&gt;Все вроде не сложно, но вот наименование точек оказалось самым сложным)&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('https://a.gavrilov.info/data/python/file.csv')
df.iloc[:, 6] = df.iloc[:, 6].str.replace('$','',regex=True)
df.iloc[:, 5] = df.iloc[:, 5].str.replace('$','',regex=True)
df.iloc[:, 5] = df.iloc[:, 5].astype(float)
df.iloc[:, 6] = df.iloc[:, 6].astype(float)
fig, ax = plt.subplots(figsize=(10, 6))
ax.scatter(x = df[&amp;quot;Cheapest 1GB for 30 days (USD)&amp;quot;], y = df[&amp;quot;Most expensive 1GB (USD)&amp;quot;])
for idx, row in df.iterrows():
 ax.annotate(row['Name'], (row['Cheapest 1GB for 30 days (USD)'], row['Most expensive 1GB (USD)']))
plt.show()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Это одно из заданий, которые встречаются на курсах по питону.&lt;/p&gt;
&lt;p&gt;Файл данных тут: &lt;a href="https://a.gavrilov.info/data/python/file.csv,"&gt;https://a.gavrilov.info/data/python/file.csv,&lt;/a&gt; это затраты на интернет в разных странах.&lt;/p&gt;
&lt;p&gt;А задание само такое: “Построим: диаграмму рассеяния по столбцам таблицы ‘Cheapest 1GB for 30 days (USD)’ и ‘Most Expensive 1GB (USD)’. В какой стране есть Интеренет стоимостью почти 800 за Гб, если верить таблице?”&lt;/p&gt;
</description>
</item>

<item>
<title>Рисую простую диаграмму на Python</title>
<guid isPermaLink="false">25</guid>
<link>https://gavrilov.info/all/risuyu-prostuyu-diagrammu-na-python/</link>
<pubDate>Sat, 09 Jul 2022 19:36:03 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/risuyu-prostuyu-diagrammu-na-python/</comments>
<description>
&lt;p&gt;Вот собственно сам код:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import pandas as pd
import altair as alt

df = pd.read_excel('Sample - Superstore.xls', index_col=0) 
df2 = df.groupby('Region').sum()
df2 = df2.reset_index(level=0)

bars = alt.Chart(df2).mark_bar().encode(
   alt.X('Region:N', sort='-y'),
   alt.Y('Sales:Q'),
   color=alt.condition(  alt.datum.Region == 'West',  # If the year is 1810 this test returns True,
   alt.value('orange'),     # which sets the bar orange.
   alt.value('steelblue'))   
)

text = bars.mark_text(
    align='left',
    baseline='middle',
    dx=11  # Nudges text to right so it doesn't appear on top of the bar
).encode(
    text='mean(Sales):Q'
)

(bars + text).properties(height=300)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/visualization.png" width="209" height="370" alt="" /&gt;
&lt;/div&gt;
</description>
</item>


</channel>
</rss>