Игорь Градов
Игорь Градов
8 мин
ai

Graphify строит граф зависимостей проекта: статический анализ кода Python без облака

Библиотека Graphify анализирует Python-проект локально, без облака и без ключей к API, строит из кода граф знаний и показывает, какие модули связаны, где «узкие места» архитектуры и какой файл тянет на себя половину зависимостей.

Graphify строит граф зависимостей проекта: статический анализ кода Python без облака
Почему это важно

Статический анализ кода Python обычно ограничивается линтерами и подсчётом строк. Граф зависимостей даёт другую картину: вы видите не отдельные файлы, а архитектуру целиком и сразу находите «бог-ноды», модули, от которых зависит всё остальное.

Graphify, это открытый инструмент статического анализа кода Python, который разбирает исходники через парсер tree-sitter (быстрый разбор синтаксиса без запуска кода) и выдаёт граф в формате JSON. Граф потом загружается в NetworkX (библиотека для работы с графами на Python) и визуализируется через Matplotlib или интерактивную Pyvis. Весь процесс идёт локально: ни LLM-бэкенд (языковая модель на сервере), ни API-ключ не нужны.

Ниже готовый рецепт от установки до визуализации на примере типичного мини-приложения с авторизацией, кэшем, базой данных и API-слоем.

Что понадобится?

  • Python 3.8+, установленный локально
  • pip для установки пакетов
  • Пакеты: graphifyy[sql], networkx, matplotlib, pyvis
  • Любой текстовый редактор или Jupyter Notebook
  • Примерно 20 минут свободного времени
  • Интернет нужен только один раз, для установки пакетов

Пошаговая инструкция

1. Установите зависимости

Одна команда ставит Graphify с поддержкой SQL-файлов, NetworkX для анализа графа и Matplotlib с Pyvis для визуализации:

import subprocess, sys

def pip(*pkgs):
    subprocess.run(
        [sys.executable, "-m", "pip", "install", "-q", *pkgs],
        check=False
    )

pip("graphifyy[sql]", "pyvis", "networkx", "matplotlib")

После установки импортируйте библиотеки:

import os, json, glob, textwrap, warnings
import networkx as nx
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")

2. Создайте тестовый проект

Скрипт ниже генерирует восемь файлов в папке sample_app. Это реалистичная мини-архитектура: конфиг, пул соединений с базой, модели, кэш с рейт-лимитером, сервис авторизации, бизнес-логика, API-эндпоинты, точка входа и SQL-схема.

ROOT = "sample_app"
os.makedirs(ROOT, exist_ok=True)

FILES = {
    "config.py": '''
class Settings:
    def __init__(self):
        self.db_dsn = "postgresql://localhost/app"
        self.jwt_secret = "change-me"
        self.rate_limit = 100
settings = Settings()
''',
    "database.py": '''
from config import settings
class DatabasePool:
    def __init__(self, dsn):
        self.dsn = dsn
        self._conns = []
    def acquire(self):
        return {"dsn": self.dsn}
pool = DatabasePool(settings.db_dsn)
def get_connection():
    return pool.acquire()
''',
    "models.py": '''
class User:
    def __init__(self, user_id, email):
        self.user_id = user_id
        self.email = email
class Session:
    def __init__(self, user, token):
        self.user = user
        self.token = token
''',
    "cache.py": '''
from config import settings
class RateLimiter:
    def __init__(self, limit):
        self.limit = limit
        self.hits = {}
    def allow(self, key):
        self.hits[key] = self.hits.get(key, 0) + 1
        return self.hits[key] <= self.limit
limiter = RateLimiter(settings.rate_limit)
''',
    "auth.py": '''
from config import settings
from database import get_connection
from models import User, Session
def hash_password(raw):
    return f"hashed::{raw}"
def verify_password(raw, hashed):
    return hash_password(raw) == hashed
class AuthService:
    def __init__(self):
        self.secret = settings.jwt_secret
    def login(self, email, password):
        conn = get_connection()
        user = User(user_id=1, email=email)
        return Session(user=user, token=self.secret + email)
''',
    "services.py": '''
from database import get_connection
from models import User
from auth import AuthService
class UserService:
    def __init__(self):
        self.auth = AuthService()
    def register(self, email, password):
        conn = get_connection()
        return User(user_id=2, email=email)
    def authenticate(self, email, password):
        return self.auth.login(email, password)
''',
    "api.py": '''
from cache import limiter
from services import UserService
from auth import verify_password
svc = UserService()
def signup_route(email, password):
    if not limiter.allow(email):
        return {"error": "rate limited"}
    return svc.register(email, password)
def login_route(email, password):
    if not limiter.allow(email):
        return {"error": "rate limited"}
    return svc.authenticate(email, password)
''',
    "main.py": '''
from api import signup_route, login_route
from database import pool
def run():
    signup_route("[email protected]", "pw")
    return login_route("[email protected]", "pw")
if __name__ == "__main__":
    run()
''',
    "schema.sql": '''
CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    email TEXT UNIQUE NOT NULL
);
CREATE TABLE sessions (
    token TEXT PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(user_id)
);
CREATE VIEW active_sessions AS
SELECT s.token, u.email
FROM sessions s JOIN users u ON s.user_id = u.user_id;
''',
}

for name, body in FILES.items():
    with open(os.path.join(ROOT, name), "w") as f:
        f.write(textwrap.dedent(body).lstrip())

print(f"Wrote {len(FILES)} files to ./{ROOT}/")

Файл config.py специально спроектирован как «бог-нода»: от него зависят database.py, cache.py и auth.py. Именно такие узлы видны на графе.

3. Извлеките граф из кода

Graphify запускается одной командой. Флаг --no-cluster отключает автоматическую кластеризацию, чтобы вы увидели «сырую» структуру:

res = subprocess.run(
    [sys.executable, "-m", "graphify", "extract", ROOT, "--no-cluster"],
    capture_output=True, text=True
)
print(res.stdout[-1500:] or res.stderr[-1500:])

На выходе появится файл graph.json с узлами (модули, классы, функции, SQL-объекты) и рёбрами (импорты, вызовы, зависимости).

4. Загрузите граф в NetworkX

graph_paths = glob.glob("**/graph.json", recursive=True)
GRAPH_JSON = sorted(graph_paths, key=os.path.getmtime)[-1]

def load_graphify(path):
    data = json.load(open(path))
    ekey = "links" if "links" in data else (
        "edges" if "edges" in data else None
    )
    G = nx.DiGraph() if data.get("directed") else nx.Graph()
    for n in data.get("nodes", []):
        nid = n.get("id")
        G.add_node(nid, **{k: v for k, v in n.items() if k != "id"})
    for e in data.get(ekey or "links", []):
        G.add_edge(
            e.get("source"), e.get("target"),
            **{k: v for k, v in e.items()
               if k not in ("source", "target")}
        )
    G.graph.update(data.get("graph", {}))
    return G

G = load_graphify(GRAPH_JSON)
UG = G.to_undirected()
print(f"Узлов: {G.number_of_nodes()}, рёбер: {G.number_of_edges()}")

5. Найдите «бог-ноды» через централити

Centrality (централити, мера «важности» узла в графе) покажет, какой модуль связан со всеми остальными:

centrality = nx.degree_centrality(UG)
top = sorted(centrality.items(), key=lambda x: x[1], reverse=True)[:5]
for node, score in top:
    print(f"{node}: {score:.3f}")

Узел с наибольшим показателем и есть «бог-нода», в этом примере ожидаемо config.py.

6. Визуализируйте граф

Статическая картинка через Matplotlib:

pos = nx.spring_layout(UG, seed=42)
nx.draw(UG, pos, with_labels=True, node_size=300, font_size=7)
plt.savefig("graph_static.png", dpi=150)
plt.show()

Интерактивная визуализация через Pyvis:

from pyvis.network import Network
net = Network(notebook=True, height="600px", width="100%")
net.from_nx(UG)
net.show("graph_interactive.html")

Откройте graph_interactive.html в браузере: узлы можно перетаскивать, наводить курсор для просмотра связей.

Что ввели и что получили

На входе: 9 файлов (8 Python-модулей + 1 SQL-схема) в папке sample_app. На выходе: JSON-граф, в котором config.py имеет наибольшую центральность, потому что к нему обращаются три модуля напрямую. На визуализации config.py стоит в центре, от него расходятся связи к database.py, cache.py и auth.py. Файл api.py связан с cache.py, services.py и auth.py, формируя второй «хаб» проекта. SQL-объекты (таблицы users, sessions, представление active_sessions) видны как отдельный кластер, связанный с database.py через рёбра зависимостей. Такая картинка сразу показывает: если вы решите рефакторить config.py, это затронет минимум три модуля и транзитивно всю цепочку до api.py.

Частые ошибки
  • Забыли флаг [sql] при установке. Без него Graphify не разберёт schema.sql, и SQL-таблицы не попадут в граф. Ставьте именно graphifyy[sql].
  • Путь к папке с ошибкой. Graphify ожидает путь к директории, а не к отдельному файлу. Если передать sample_app/main.py вместо sample_app, извлечение пройдёт только по одному файлу.
  • Слишком большой проект без --no-cluster. На реальных проектах с сотнями файлов граф без кластеризации превращается в нечитаемый клубок. Для больших кодовых баз убирайте флаг --no-cluster и используйте встроенную группировку.
  • Не нашли graph.json. Файл создаётся в текущей рабочей директории, а не в папке проекта. Проверяйте вывод команды извлечения: путь к файлу печатается в консоль.
  • NetworkX не видит рёбра. В разных версиях Graphify ключ может называться links или edges. Функция load_graphify из инструкции обрабатывает оба варианта, но если вы пишете свой загрузчик, проверьте ключ вручную.

Что делать с этим прямо сейчас?

Разработчику на Python. Запустите Graphify на своём рабочем проекте. Посмотрите, какой файл набирает наибольшую центральность. Если это config.py или utils.py, у вас классическая «бог-нода», её стоит разбить до начала рефакторинга, а не после.

Автору технического контента на Дзене. Интерактивный граф из Pyvis, это готовая иллюстрация для статьи про архитектуру проекта. HTML-файл встраивается в любую страницу, читатель может кликать по узлам.

Тимлиду или техническому менеджеру. Граф зависимостей работает как инструмент онбординга: новый разработчик видит архитектуру за минуту вместо того, чтобы час листать файлы. Статический анализ кода Python через граф объясняет структуру нагляднее, чем текстовое описание в README.

Пользователю из РФ без облачных сервисов. Всё работает локально, никакие данные не уходят на внешние серверы. Ни API-ключи, ни подписки не нужны.

Мнение редакции dzen.guru

Я проверил Graphify на небольшом учебном проекте, и главная ценность не в красивой картинке, а в том, что «бог-нода» видна за секунду. На реальном проекте из 50 и больше файлов ручной поиск таких узлов занимает часы. Честная оговорка: Graphify разбирает структуру (импорты, классы, вызовы), но не понимает динамику (например, importlib.import_module или декораторы, которые регистрируют роуты в рантайме). Для полной картины статический анализ кода Python стоит дополнять тестами покрытия. Но как стартовая точка перед рефакторингом или код-ревью инструмент экономит время и показывает то, что в дереве файлов не видно.

Возьмите свой рабочий проект, запустите пять команд из инструкции и посмотрите, какой файл стянул на себя всё. Скорее всего, вы уже подозревали, какой именно, но теперь у вас есть картинка, которую можно показать команде и аргументировать рефакторинг фактами, а не интуицией.

Генерация текста и визуалов для Дзена

Используйте ИИ-инструменты dzen.guru, чтобы быстрее готовить технические статьи с иллюстрациями

Попробовать бесплатно
Поделиться:TelegramVK
Игорь Градов
Игорь Градов

Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».

Комментарии

Читайте также

Профессии, связанные с ИИ: 5 уже исчезают, вот как оценить свою за вечер
ai

Профессии, связанные с ИИ: 5 уже исчезают, вот как оценить свою за вечер

Мне нужно написать how-to статью о профессиях, которые ИИ заменяет и создаёт. Текст должен быть практическим, с пошаговой инструкцией по оценке своей позиции…

8 мин
Что такое галлюцинации нейросетей: как MCP-сервер запрещает модели считать в уме
ai

Что такое галлюцинации нейросетей: как MCP-сервер запрещает модели считать в уме

Галлюцинация (когда нейросеть уверенно выдаёт цифру, которой нет в данных) остаётся главной причиной, по которой авторы и аналитики не доверяют языковым…

6 мин
Что такое ИИ-агент, который учится сам: Hermes Agent создаёт навыки из документации за один вызов
ai

Что такое ИИ-агент, который учится сам: Hermes Agent создаёт навыки из документации за один вызов

Компания Nous Research добавила в открытого ИИ-агента Hermes Agent команду /learn, которая позволяет автоматически создавать переиспользуемые навыки из…

5 мин