HTTP-запросы, venv и упаковка
Полезный Python-инструмент — это два навыка: HTTP-запросы через requests (всегда таймаут, raise_for_status, ключ из env) и изоляция через venv, чтобы зависимости одного проекта не ломали другой.
Ты пишешь 30-строчный Python-скрипт, который зовёт LLM API, чтобы суммировать пачку тикетов. На твоём ноутбуке он работает. Передаёшь коллеге — pip install requests падает: его глобальный Python уже пинит несовместимый urllib3 ради другого проекта. Это чинишь, и скрипт виснет навсегда на одном запросе, потому что у провайдера был сбой, а ты не задал таймаут. Потом ревью безопасности находит API-ключ, захардкоженный в файле, который ты только что запушил. Ни одна из этих проблем не сложная — это ровно три вещи, на которых спотыкается JS/TS-разработчик, приходящий в Python: небрежный HTTP, отсутствие изоляции окружения и секреты в исходниках.
За несколько минут ты увидишь, почему ровно три провала коллеги случаются и какое минимальное изменение предотвращает каждый из них до попадания в прод.
HTTP через requests: четыре вещи, которые нельзя пропускать
requests — де-факто HTTP-клиент Python: для Python он то же, что fetch для браузера, только синхронный и куда удобнее стандартного urllib. GET с query-параметрами и JSON-ответом — это три строки:
import requests
resp = requests.get(
"https://api.example.com/search",
params={"q": "python", "limit": 10},
headers={"Accept": "application/json"},
timeout=10,
)
resp.raise_for_status() # превратить 4xx/5xx в исключение
data = resp.json() # разобранный dict, как await resp.json() у fetchPOST с JSON — той же формы: передаёшь json=, и requests сериализует тело и сам ставит Content-Type: application/json:
resp = requests.post(url, json={"name": "ada"}, timeout=10)Две привычки отличают скрипт, который выживает в проде, от того, что виснет или врёт:
- Всегда передавай
timeout=. Это сеньорский момент и тот, что упускают новички. По умолчаниюrequestsждёт вечно. Один медленный или мёртвый апстрим заморозит весь твой скрипт — или каждый воркер в пуле — без ошибки и без восстановления. Уfetchтот же пробел (нуженAbortController); в Python ты просто передаёшь число. - Вызывай
raise_for_status(). В отличие отfetch, который отклоняется только при сетевом сбое и спокойно резолвит 500,requestsпо умолчанию не бросает исключение на 4xx/5xx —respвыглядит нормально, аresp.json()может вернуть тело с ошибкой, которое ты примешь за данные.raise_for_status()превращает плохой статус вHTTPError, который можно поймать. (fetch— зеркало: там ты сам проверяешьresp.ok.)
Для множества параллельных вызовов или async-приложения бери httpx — та же поверхность API, что у requests, но с async/await и HTTP/2. Retry с backoff стоит завести для любого реального апстрима: на 429 или 503 подожди 0.5с, 1с, 2с… и повтори несколько раз, а не падай на первом же сбое. (Библиотеки вроде tenacity или адаптер Retry из urllib3 делают это за тебя.)
Выигрыш для AI-склейки: безопасный вызов LLM HTTP API
Бо́льшая часть «AI-интеграции» — ровно этот паттерн: POST JSON-тела на эндпоинт провайдера с заголовком авторизации, затем разбор JSON, который вернулся. Сеньорская деталь — где живёт ключ: никогда в исходниках.
import os
import requests
api_key = os.environ.get("LLM_API_KEY") # читаем из окружения, не из файла
if not api_key:
raise RuntimeError("LLM_API_KEY не задан")
resp = requests.post(
"https://api.provider.example/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": "some-model",
"messages": [{"role": "user", "content": "Суммируй этот тикет: ..."}],
},
timeout=30, # LLM медленные; дай им запас, но ограничь
)
resp.raise_for_status()
reply = resp.json()["choices"][0]["message"]["content"]os.environ["LLM_API_KEY"] — это Python-аналог process.env.LLM_API_KEY. Ты задаёшь его в shell, в .env, загружаемом через python-dotenv, или в хранилище секретов деплой-платформы — и ключ никогда не появляется в репозитории, в git history или при демонстрации экрана. Хардкод ключа — самый частый способ утечки учётных данных. Щедрый timeout=30 тут тоже важен: ответы модели медленные, поэтому тесный таймаут отменяет хорошие запросы, а отсутствие таймаута означает, что одна застрявшая генерация вешает твой инструмент бесконечно.
| Node / TypeScript | Python | Примечание |
|---|---|---|
fetch() | requests / httpx | requests синхронный; httpx добавляет async + HTTP/2 |
await resp.json() | resp.json() | синхронно; без await |
сам проверяешь resp.ok | resp.raise_for_status() | ни тот, ни другой не бросают на 4xx/5xx по умолчанию |
AbortController | timeout= | без него requests ждёт вечно |
process.env.X + dotenv | os.environ[“X”] + python-dotenv | держи секреты вне исходников |
package.json | pyproject.toml / requirements.txt | манифест проекта + зависимостей |
node_modules/ | .venv/ | место установки для одного проекта |
npm install | pip install (или uv) | разрешение + установка зависимостей |
Твой скрипт зовёт LLM API и иногда виснет на минуты без ошибки. Самая вероятная причина?
Провайдер возвращает 500 с JSON-телом ошибки. Твой код делает resp.json() без raise_for_status(). Что произойдёт?
Окружения и упаковка: зачем нужен venv
Спроси себя: сколько Python-проектов у тебя или коллег на одной машине? Каждый предъявляет свои требования к версиям библиотек — а по умолчанию Python позволяет им всем делить одну глобальную папку установки. Вот провал коллеги из хука. Python по умолчанию ставит пакеты в один глобальный site-packages, общий для каждого проекта на машине. Проекту A нужен urllib3==1.26; проекту B нужен urllib3==2.x. При глобальной установке второй pip install перезаписывает первый — и теперь тот проект, что загрузит неверную версию, ломается. Нет node_modules на проект, чтобы их развести.
Виртуальное окружение (venv) — и есть эта граница на проект. Это лёгкая изолированная копия интерпретатора со своим site-packages, поэтому зависимости каждого проекта живут в своей папке и никогда не сталкиваются:
python -m venv .venv # создать изолированное окружение в ./.venv
source .venv/bin/activate # macOS/Linux (Windows: .venv\Scripts\activate)
pip install requests httpx # ставит В .venv, не глобальноПока окружение активно, python и pip указывают на .venv — аналогично тому, как node_modules ограничивает пакеты одним проектом, только venv изолирует сам интерпретатор, а не одни библиотеки. Папку .venv/ добавляешь в .gitignore (как node_modules/), а вместо неё коммитишь манифест, чтобы кто угодно мог её пересоздать.
Пиннинг: requirements.txt и переход к pyproject.toml
Изоляция — половина истории; вторая половина — воспроизводимость. Если ты pip install requests сегодня, а коллега через месяц, вы можете получить разные версии. Пинни их. Классический файл — requirements.txt:
requests==2.32.3
httpx==0.27.2Воссоздаёшь точный набор через pip install -r requirements.txt внутри свежего venv. Это package.json + lock-файл, разнесённые по соглашению, а не в один файл. Современный стандартизированный дом для метаданных и зависимостей проекта — pyproject.toml (PEP 621): объявление зависимостей там плюс быстрый резолвер вроде uv или poetry даёт настоящий lock-файл и настройку одной командой, гораздо ближе к опыту npm install. Для быстрого скрипта requirements.txt сойдёт; для всего, что ты отгружаешь или делишь, предпочитай pyproject.toml.
▸Почему это работает
Почему не просто pip install --user или установить глобально и пропустить venv? Потому что «глобально» — это разделяемое изменяемое состояние на каждый проект и даже на собственный Python-тулинг ОС. Пин одного проекта тихо меняет рантайм другого, апгрейды превращаются в русскую рулетку, а на некоторых системах pip в системный Python заблокирован напрочь (PEP 668, «externally-managed-environment»). venv на проект делает зависимости явными, одноразовыми и воспроизводимыми — удали .venv, пересоздай из манифеста, готово.
Ты начинаешь небольшой Python-инструмент, который будут запускать и двое коллег. Как управлять его зависимостями?
Расставь шаги, чтобы настроить и запустить HTTP-инструмент, безопасно зовущий LLM API:
- 1 python -m venv .venv — создать изолированное окружение
- 2 source .venv/bin/activate — активировать, чтобы pip/python указывали на него
- 3 pip install -r requirements.txt — поставить пиннутые зависимости в venv
- 4 export LLM_API_KEY=… (или загрузить .env) — ключ в окружении, не в исходниках
- 5 python tool.py — POST с заголовком авторизации + таймаутом, затем raise_for_status()
- 01Пройди по безопасному POST к LLM API в Python: что нужно задать и чем каждое отличается от JS fetch?
- 02pip install коллеги ломает другой проект. Объясни почему и фикс через venv + манифест от начала до конца.
Полезный Python-инструмент — это два навыка, работающих вместе. Первый — HTTP: requests — это Python-аналог fetch, но синхронный: requests.get/post с params/json и resp.json() без await. Сеньорские привычки не обсуждаются: всегда передавай timeout= (иначе requests ждёт вечно, и один застрявший апстрим вешает весь скрипт), всегда вызывай raise_for_status() (requests не бросает на 4xx/5xx, поэтому тело ошибки проходит как данные), а для множества или async-вызовов бери httpx плюс retry с backoff на 429/503. Выигрыш для AI-склейки той же формы: POST JSON-тела с заголовком Authorization: Bearer провайдеру, читай ключ из os.environ, чтобы он не жил в исходниках, и дай медленным моделям щедрый, но ограниченный таймаут. Второй — окружения: Python по умолчанию ставит глобально, поэтому пины зависимостей двух проектов сталкиваются — venv на проект и есть изолирующая граница (свой интерпретатор и site-packages), в gitignore как node_modules. Пинни версии в requirements.txt или, лучше, в pyproject.toml с uv/poetry, чтобы точное окружение было воспроизводимо на любой машине. Изоляция плюс пиннинг — вот что превращает «работает на моём ноуте» в инструмент, который коллеги реально могут запустить. Теперь, когда встретишь зависший скрипт или сломанный pip install у коллеги, — знаешь, какие две строки добавить первыми: timeout= и venv.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.