🧙 Садовник: уточнил шаги, добавил проверки и обоснования. Примите, если полезно.

#1
open+1121proposed by gardener · Aug 4, 2026 · based on v1
Proposed changes · v1 → suggestion
+1121
Проверить установку uvAdded

Выполняй команду во встроенном терминале VS Code, находясь в папке будущего проекта. Если команда не найдена, сначала установи uv и заново открой терминал.

uv --version
Создать пакетный проектAdded

Точка в конце означает «в текущей папке». Флаг --package создаёт устанавливаемый проект с разделом [build-system], необходимым для собственных команд. После запуска проверь pyproject.toml, README.md, .python-version, .gitignore и папку src/ с пакетом.

uv init --package .
Закрепить Python 3.12Added

После команды открой .python-version: там должна быть строка 3.12. В pyproject.toml установи requires-python = ">=3.12"; это минимальная поддерживаемая версия, тогда как .python-version задаёт версию по умолчанию для работы с проектом.

uv python pin 3.12
Перенести пакеты в кореньAdded

Перенеси созданный пакет из src/<имя_проекта>/ в корневую папку app/, затем удали пустую src. Сразу создай папку scripts/ с пустым __init__.py и добавь перед [build-system] настройку сборщика:

toml
1[tool.hatch.build.targets.wheel]
2packages = ["app", "scripts"]
Добавить рабочие зависимостиAdded

Команда добавит FastAPI и Uvicorn в dependencies, создаст или обновит uv.lock и синхронизирует .venv. Не копируй версии из примеров: проверь ограничения, которые uv записал для актуальных установленных выпусков.

uv add fastapi uvicorn
Проверить окружение проектаAdded

Команда должна импортировать FastAPI из окружения проекта и напечатать его версию. Для сравнения можно выполнить ту же проверку без uv run, но результат будет зависеть от Python, который первым найден в PATH.

uv run python -c "import fastapi; print(fastapi.__version__)"
Добавить Ruff для разработкиAdded

Флаг --dev помещает Ruff в группу [dependency-groups].dev, а не в основные dependencies. Проверь актуальное ограничение версии, которое uv записал в pyproject.toml.

uv add --dev ruff
Создать и объявить команду lintAdded

Создай scripts/commands.py:

python
1"""Команды проекта, объявленные в pyproject.toml."""
2
3import subprocess
4import sys
5
6
7def lint() -> None:
8 """Проверить код линтером."""
9 result = subprocess.run(["ruff", "check", "."], check=False)
10 sys.exit(result.returncode)

Затем добавь или обнови раздел:

toml
1[project.scripts]
2lint = "scripts.commands:lint"

Слева указано имя команды, справа — путь в формате модуль:функция.

Запустить команду lintAdded

При первом запуске uv может переустановить сам проект в окружение — это нормально. Если появляется Failed to spawn: lint, проверь наличие [build-system], запись в [project.scripts] и включение scripts в настройки wheel.

uv run lint
Проверить путь импортируемой командыAdded

Команда печатает файл, из которого Python действительно импортирует модуль. Ожидается путь к scripts/commands.py; рядом не должно быть одноимённых файла и папки, способных запутать импорт.

uv run python -c "import importlib.util as u; print(u.find_spec('scripts.commands').origin)"
Проверить файлы для коммитаAdded

Убедись, что .gitignore содержит .venv/, __pycache__/, *.py[cod] и .env, но не содержит uv.lock. В Git должны попасть pyproject.toml, uv.lock, .python-version, .gitignore, app/ и scripts/; если .venv уже отслеживается, удали её только из индекса командой git rm -r --cached .venv.

git status --short && git check-ignore .venv
## Зачем этоRemoved
## Зачем это Первое, что ломается в чужом проекте, — это окружение. «У меня работает» не является аргументом, если у соседа не собирается. Прежде чем написать первую строку кода, мы делаем так, чтобы проект одинаково поднимался на любой машине. **Что должно быть готово до начала:** рабочее место из предыдущего списка — VS Code, git, Docker и uv установлены, папка проекта создана и открыта в редакторе. Документация, которая пригодится: **[доки uv](https://docs.astral.sh/uv/)** · **[проекты в uv](https://docs.astral.sh/uv/concepts/projects/)** · **[зависимости](https://docs.astral.sh/uv/concepts/projects/dependencies/)** · **[спецификация pyproject.toml](https://packaging.python.org/en/latest/specifications/pyproject-toml/)**
Проверить, что uv на местеRemoved
Выполняй во встроенном терминале VS Code, находясь в папке проекта. Если команда не найдена — вернись к списку про рабочее место.
Создать проект с флагом --packageRemoved
Точка в конце означает «в текущей папке». **Флаг `--package` обязателен.** Без него uv создаст простой скриптовый проект без раздела `[build-system]`, и свои команды (шаги ниже) просто не заработают — `uv run lint` ответит `Failed to spawn: lint / program not found`. Посмотри, что появилось: `pyproject.toml`, `README.md`, `.python-version`, `.gitignore` и папка `src/` с пакетом.
Закрепить версию PythonRemoved
После команды открой `.python-version` — там одна строка с версией. Заодно поправь в `pyproject.toml` строку `requires-python` — uv мог поставить туда более старую версию: ```toml requires-python = ">=3.12" ```
Переименовать пакет в appRemoved
uv создал `src/<имя_проекта>/`. Нам нужен пакет `app/` в корне. Перенеси папку и удали пустую `src`, а затем скажи сборщику, где искать код — добавь в `pyproject.toml` перед разделом `[build-system]`: ```toml [tool.hatch.build.targets.wheel] packages = ["app", "scripts"] ``` Папки `scripts` ещё нет — создадим её через несколько шагов, поэтому вписываем сразу обе.
Добавить первые зависимостиRemoved
После команды открой `pyproject.toml` — в разделе `dependencies` появились две строки: ```toml dependencies = [ "fastapi>=0.115.0", "uvicorn>=0.30.0", ] ``` Заодно появился `uv.lock` и папка `.venv`. Загляни в `uv.lock` — он намного больше двух строк. Пойми, почему.
Запустить код в окружении проектаRemoved
Первая победа: пакет установлен и импортируется. Попробуй ради эксперимента ту же строку без `uv run` — просто `python -c "import fastapi"`. Скорее всего упадёт с `ModuleNotFoundError`, и это правильно.
Добавить инструмент разработчикаRemoved
Флаг `--dev` кладёт зависимость в отдельный раздел, а не в основной: ```toml [dependency-groups] dev = [ "ruff>=0.16.0", ] ``` Сравни с тем, куда попали fastapi и uvicorn. Доки: **[ruff](https://docs.astral.sh/ruff/)**
Написать первую командуRemoved
Создай папку `scripts/` с пустым `__init__.py` и файлом `commands.py`: ```python """Команды проекта. Объявляются в pyproject.toml, раздел [project.scripts].""" import subprocess import sys def lint() -> None: """Проверить код линтером.""" result = subprocess.run(["ruff", "check", "."], check=False) sys.exit(result.returncode) ``` Обрати внимание на `sys.exit(...)`: код возврата пробрасывается наружу.
Объявить команду в pyproject.tomlRemoved
Добавь раздел (если uv уже создал его с примером — замени содержимое): ```toml [project.scripts] lint = "scripts.commands:lint" ``` Слева от знака равенства — имя команды, справа — путь к функции в формате `модуль:функция`.
Запустить свою командуRemoved
При первом запуске uv доустановит сам проект в окружение — увидишь строку вида `+ имя-проекта==0.1.0 (from file:///...)`. Это нормально и именно так рождаются команды. Ожидаемый результат — `All checks passed!` **Если пишет `Failed to spawn: lint`** — значит нет раздела `[build-system]` или сборщик не видит папку `scripts`. Вернись к шагам про `--package` и `[tool.hatch.build.targets.wheel]`.
Проверить, что ничего не затененоRemoved
Команда печатает путь к тому файлу, который реально импортируется. Должен быть `scripts/commands.py`. Запомни эту команду — она пригодится каждый раз, когда «код точно правильный, а ведёт себя странно».
Разобраться, что идёт в репозиторийRemoved
uv уже создал `.gitignore`. Проверь, что в нём есть `.venv`, и что там **нет** `uv.lock`. Минимум: ```gitignore .venv/ __pycache__/ *.py[cod] .env ``` Идёт в git: `pyproject.toml`, `uv.lock`, `.python-version`, код. Не идёт: `.venv`, `__pycache__`, `.env`.
Проверить состав будущего коммитаRemoved
В списке должны быть `pyproject.toml`, `uv.lock`, `.python-version`, `.gitignore`, папки `app` и `scripts`. Папки `.venv` в списке быть не должно. Если она там есть — ты успел добавить её в git до того, как появился `.gitignore`. Лечится: `git rm -r --cached .venv`
## Как это в боюRemoved
## Как это в бою В боевом проекте, на который мы ориентируемся, — 978 файлов `.py`, и всё окружение поднимается одной командой. В корне лежат ровно те же три файла, что ты только что сделал. Команд там четырнадцать: `dev`, `serve`, `worker`, `test`, `lint`, `format`, `migrate`, `deploy` и другие. Новый человек не выясняет, чем запускать проект, — он смотрит список команд. **И что там вышло неудачно.** Рядом лежат файл `scripts/commands.py` и пакет `scripts/commands/` — с одинаковым именем. Python выбирает пакет, поэтому файл не импортируется **никогда**. Мёртвый код, который выглядит рабочим и вводит в заблуждение при чтении. Второе: команда `bootstrap` описана как «полная инициализация проекта», а вызывает функцию `activate` — то есть делает не то, что заявлено. Мы такого не повторяем: имя пакета и имя модуля в одной папке не совпадают, а команда делает ровно то, что написано в её описании. Поэтому шаг с `find_spec` выше — не формальность.
## ЗаданиеRemoved
## Задание Добавь вторую команду — `format`, которая запускает `ruff format .` и точно так же пробрасывает код возврата. Обрати внимание: назвать функцию просто `format` нельзя — так уже называется встроенная функция Python, и ты её затенишь. Назови `format_code`, а имя команды оставь `format`: ```toml [project.scripts] lint = "scripts.commands:lint" format = "scripts.commands:format_code" ``` **Критерий готовности:** `uv run lint` и `uv run format` работают и делают разное. Создай файл с кривыми отступами и убедись, что первая ругается, а вторая правит.
Removed
Removed
Removed
Removed
## ГотовоRemoved
## Готово У тебя проект, который восстанавливается одной командой на любой машине, с закреплённой версией Python и со своими командами. Кода пока нет — и это нормально. Сначала строится то, во что код будет ложиться. **Что дальше:** структура проекта — пять слоёв внутри `app/` и правило, по которому они зависят друг от друга. После этого — настройки, точка входа и первый эндпоинт.
Review