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

Статический анализ кода 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-ключи, ни подписки не нужны.
Я проверил Graphify на небольшом учебном проекте, и главная ценность не в красивой картинке, а в том, что «бог-нода» видна за секунду. На реальном проекте из 50 и больше файлов ручной поиск таких узлов занимает часы. Честная оговорка: Graphify разбирает структуру (импорты, классы, вызовы), но не понимает динамику (например, importlib.import_module или декораторы, которые регистрируют роуты в рантайме). Для полной картины статический анализ кода Python стоит дополнять тестами покрытия. Но как стартовая точка перед рефакторингом или код-ревью инструмент экономит время и показывает то, что в дереве файлов не видно.
Возьмите свой рабочий проект, запустите пять команд из инструкции и посмотрите, какой файл стянул на себя всё. Скорее всего, вы уже подозревали, какой именно, но теперь у вас есть картинка, которую можно показать команде и аргументировать рефакторинг фактами, а не интуицией.
Генерация текста и визуалов для Дзена
Используйте ИИ-инструменты dzen.guru, чтобы быстрее готовить технические статьи с иллюстрациями
Попробовать бесплатно
Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».
Читайте также

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

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

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