Переиспользуем Python-функции между проектами — без переписывания и передеплоя

Переиспользуем Python-функции между проектами — без переписывания и передеплоя - 1

У меня есть функция. Крошечная — десяток строк, которые приводят суммы из платёжек к нормальному виду: "1 234,50 ₽" превращают в 1234.5. Ничего гениального. Но я писал её, кажется, раз пять. В парсере банковских выписок, в импортёре заказов, в отчёте для бухгалтерии, в скрипте, который потом выкинул, и ещё где-то, о чём уже забыл.

И каждый раз — чуть по-другому. Где-то забыл про неразрывный пробел. Где-то евро вместо рубля. В одном месте нашёл баг с минусом у отрицательных сумм и починил — а в остальных четырёх копиях он так и остался, потому что кто вообще помнит, где ещё живёт этот код.

Если что-то из этого знакомо — думаю, дальше будет полезно. Это первая из нескольких статей про splime — инструмент, который вырос ровно из этой задачи: переиспользовать код между проектами, не копируя его. Начну с общей картины: что это, зачем и как выглядит в работе.

Что мы вообще делаем с такими функциями

Когда полезная функция нужна во втором проекте, вариантов обычно три, и каждый по-своему раздражает.

Скопировать. Быстро и честно ровно до того момента, пока копий не станет три. Дальше начинается то, с чего я начал: правку в одной копии остальные не видят, и функция потихоньку расползается на пять слегка разных версий с разными багами.

Вынести в общий пакет. Взрослое решение — и правильное, когда функций много. Но ради одной clean_amount вы теперь версионируете пакет, публикуете его, тянете целиком в каждый проект (со всеми его зависимостями) и обновляете везде, где он прижился. Многовато возни ради десятка строк.

Завернуть в микросервис. Тоже вариант — если это сервис. Но clean_amount не сервис. Писать HTTP-обвязку, деплой и следить за аптаймом ради нормализации строки — это из пушки по воробьям.

А по сути нужно одно: опубликовать функцию один раз и звать её по имени откуда угодно. Не таскать код, не тянуть зависимости, не поднимать сервис. Позвал по имени — получил результат. Вот из этого желания и вырос splime.

splime за шесть строк

Ставим пакет (нужен Python 3.13+) и поднимаем локальный демон — это фоновый процесс, который будет хранить ваши функции и запускать их в любом проекте и окружении:

pip install splime
spl-daemon serve        # поднимет локальный демон на http://127.0.0.1:8765

Теперь та самая функция. Публикуем её как объект-ноду — версионированную, переносимую единицу — и зовём по имени:

from spl import SPLClient

def clean_amount(raw: str) -> float:
    # "1 234,50 ₽"  ->  1234.5
    cleaned = raw.replace(" ", "").replace("xa0", "").replace("₽", "")  # xa0 - неразрывный пробел
    return float(cleaned.replace(",", "."))

client = SPLClient()                       # локально; на сервер ничего не уходит
client.publish(clean_amount, name="clean_amount")

result = client.call("clean_amount", kwargs={"raw": "1 234,50 ₽"})
print(result.mode)     # "local"
print(result.output)   # 1234.5
Первый запуск: поднимаем демон, публикуем clean_amount и зовём по имени - в ответ 1234.5.

Первый запуск: поднимаем демон, публикуем clean_amount и зовём по имени — в ответ 1234.5.

Всё. Это весь цикл: определили функцию, publish — и она стала объектом локальной библиотеки (нодой); call по имени — и вернулось значение. Причём из любого другого скрипта на этой машине достаточно client.call("clean_amount", ...) — саму функцию второй раз писать не нужно, она уже опубликована.

Вернулось, кстати, не только число. У результата есть result.output (само значение), result.mode (где выполнялось) и result.artifacts — файлы, которые нода сложила по ходу дела. Плюс у запуска остаётся история: что вызывали, с какими аргументами, чем завершилось даже в случае ошибки или падения.

Что здесь только что произошло

Три вещи, которые стоит удержать в голове, которые демонстрируют явно почти всю идею.

Нода — это ваша функция, упакованная в версионированную переносимую форму. Не pickle, не «слепок процесса», а разобранное описание функции, которое можно хранить, переносить и воспроизводить. При этом обо всех зависимостях и окружении уже позаботились фреймворк и локальный сервис splime.

Демон — локальный сервис. Он хранит опубликованные объекты, собирает под них окружение и запускает исполнение, в том числе в Docker-контейнере. Живёт у вас на локальной машине.

Local-first. Обратите внимание: SPLClient() без всяких токенов работает полностью локально и никуда не ходит по сети. Никакого обязательного облака, аккаунта и «войдите, чтобы продолжить». Сервер подключается позже и только если он вам нужен.

Схема: SPLClient публикует и зовёт ноды; локальный демон хранит их в библиотеке, собирает окружение и исполняет.

Схема: SPLClient публикует и зовёт ноды; локальный демон хранит их в библиотеке, собирает окружение и исполняет.

«А если данные не у меня на машине?»

Вот тут — самое интересное, и ровно то, ради чего всё затевалось. Допустим, у вас опубликована нода потяжелее — score_batch, которой нужны данные и GPU с другой машины. Тот же самый вызов становится удалённым, стоит указать, где выполнять:

result = client.call(
    "score_batch",
    kwargs={"date": "2026-06-08"},
    target_machine="gpu-box",     # выполнить там, где живут данные/GPU/доступы
)
print(result.mode)     # "server"

Функция может ходить в базу, которой нет на вашем компьютере. Ей может быть нужен GPU или доступы, которые есть только на одной конкретной удаленной машине. Код при этом не переезжает к данным вручную и не дублируется — вы зовёте ноду по имени, а выполняется она там, где надо. Как это устроено внутри — тема следующих статей; пока достаточно того, что вызов один и тот же, меняется только «где». (Для удалённого запуска нужно просто подключить очередной локальный демон целевой машины к серверу splime – https://splime.io/app/ — про настройку расскажу дальше в серии; локально же всё работает сразу после pip install.)

Console на splime.io/app: подключённые машины-демоны, библиотеки и история запусков.

Console на splime.io/app: подключённые машины-демоны, библиотеки и история запусков.

Сразу про рамки: где splime, а где — другие инструменты

Чтобы не было завышенных ожиданий — и чтобы сэкономить нам время в комментариях.

Для расписаний и сложных DAG’ов есть прекрасные оркестраторы: Airflow, Prefect, Temporal. Сегодня splime не про это — он не гоняет пайплайны по расписанию, а даёт переиспользуемые ноды, которые такой оркестратор может вызывать. Одно неплохо ложится на другое.

Для массивной параллельности — размазать одну задачу на сотни ядер — есть Ray и родственные ему штуки. splime не соревнуется с ними за пропускную способность: его забота — версии, переносимость и вызов той или иной функции “просто по имени”.

Публичного маркетплейса кода splime сегодня тоже не строит: ноды приватные, вы публикуете их для себя и своей команды, с явным владением и доступами, а не выкладываете в общую витрину.

И это не просто “запусти любой код где угодно”: splime исполняет функции, которым вы осознанно доверяете, на машинах, которые вы контролируете, — граница доверия проходит там же, где всегда: на том, что вы сами решили опубликовать.

Если одной строкой: splime превращает проверенные Python-функции в версионированные переносимые ноды, которые можно переиспользовать между проектами и запускать локально или удалённо.

Стадия готовности splime

splime — молодой проект. Это alpha: публичный API ещё может меняться между релизами, и мы не притворяемся, что за плечами годы продакшена и толпы пользователей. Открыто главное — сам фреймворк и локальный демон, то есть весь локальный движок: он ставится из PyPI, а в код можно залезть на GitHub и посмотреть, как всё устроено, а не верить мне на слово. Сервер и веб-панель (Console на splime.io/app), через которые демоны собираются в команду и появляется удалёнка, — управляемая часть, её мы держим у себя. Если идея выглядит для вас интересно или, наоборот, кажется, что мы делаем ерунду, — сейчас как раз тот момент, когда обратная связь влияет на продукт.

Попробовать: pip install splime (Python 3.13+). Код, issue и звезда, если зашло: github.com/yastrebovks/splime. Документация и честный статус проекта: splime.io.

Что дальше в серии

Это была общая картина. Дальше мы планируем копнуть вглубь, рассказа о тех или иных инженерных решениях, которые мы приняли в ходе реализации, а также причины такого выбора; благо локальный движок открыт, и по ходу можно сверяться с кодом:

  1. Локальный уровень. Как устроен демон, где и в каком виде живут ноды и как две ноды передают данные друг другу: адаптеры, теги артефактов и почему ребро между нодами это файл, а не pickle через pipe.

  2. Окружения. Как демон собирает venv через uv и как выбрать рантайм отдельно для каждой ноды: native, venv-subprocess или docker.

  3. Запуски как данные. Пайплайн упал на третьей ноде — смотрим манифест запуска, меняем адаптер и продолжаем с места падения, не пересчитывая то, что уже посчитано.

  4. Куда мы ведём проект. Command-ноды, декларации, ноды прямо из GitHub-репозитория и первый не-Python язык и почему мы принимаем именно такие решения (включая подходы, которые попробовали и осознанно выкинули).

splime молодой, и то, куда он пойдёт дальше, во многом зависит от таких обсуждений. Если что-то показалось спорным, неудобным или, наоборот, попало в точку — напишите в комментариях или заведите issue на GitHub. Из этих разговоров вырастут и следующие статьи серии, и приоритеты в роадмапе. Я рядом и отвечу.

Автор: YastrebovKS

Источник

Оставить комментарий