VS Code, python, контейнеры — как обуздать эту триаду
Это небольшой туториал о настройке VS Code для работы с python. Здесь вы не увидите каких-то божественных откровений — тут будет просто мой опыт о том, как сделать свою работу/хобби немного комфортнее и почему я пришел именно к такой конфигурации.
Мой путь к разработке начался через администрирование, и, как и любой админ, я сталкивался с необходимостью писать автоматизацию. Bash/python/powershell/go — это все типичные инструменты инженера. Но в большинстве своем мы пишем автоматизацию в vim/nano/notepad++. И на все рассказы про IDE я лишь пожимал плечами — «а мне то это зачем?». Но однажды появился проект на Python, где я стал писать код уже «в промышленных масштабах»… Результат предсказуем — я пересел на VS Code.
Почему он? Да все просто:
- Быстрый
- Удобный
- Бесплатный
- Куча плагинов
Я осознал прелесть линтера (линтер (linter) — это программа, которая автоматизирует «причёсывание» кода по определённым правилам), подсказки синтаксиса, и прочие прелести современных IDE.
Но разбираться в VS Code «все еще было некогда». И в один прекрасный момент за эту лень я поплатился сполна — у меня закрылся VScode с ошибкой и при запуске у меня открылось пустое окно. Это был шок. Привычка писать в пустых файлах, кое-как настроенные параметры запуска отладки, настройки linter-а и подсказок по синтаксису — все это в полной мере дало о себе знать. И в итоге было принято решение разобраться что тут к чему и как мне сохранить свои настройки.
В итоге мое изучение VS Code разделилось на три этапа:
- Единые настройки на все проекты.
С этого я начал, но очень быстро понял, что мне необходимо делать отдельные настройки для разных проектов. - Отдельные настройки под каждый проект.
Почти идеальный вариант, если бы не проблемы со сборкой некоторых специфических библиотек под MacOS. - Разработка в контейнерах.
Очень интересный вариант разработки, лично мне он показался очень удобным.
Из коробки VS Code можно назвать «весьма продвинутым блокнотом». В общем то так бы все и осталось, если бы не система плагинов — с их помощью можно превратить VS Code практически во что угодно.
Теперь немного теории, чтобы мы одинаково понимали используемые термины и у нас был единый контекст. Почему это в моем понимании важно? Да потому что я мучался с этим достаточно долго, пока не собрал свою порцию шишек и граблей.
Понятия:
- Workspace
В VS Code работа очень многое крутиться вокруг такого понятия как Workspace. В заложенной логике Workspace — это папка с вашим проектом, область применение настроек и параметров запуска отладки.
Системные папки в Workspace и их назначение: - .vscode
Точка в начале — обязательна. В этой папке vscode ищет настройки окружения и запуска отладки для данного Workspace. - .devcontainer
Точка в начале — обязательна. В этой папке vscode ищет настройки контейнеров и Dockerfile для сборки для данного Workspace.
Область видимости настроек:
- User
Настройки сохраняются для конкретного пользователя - Workspace
Настройки сохраняются для конкретной области
Файлы и их назначение:
- %name%.code-workspace
Настройки для данного Workspace. Может содержать настройки плагинов, путей к интерпретатору и тому подобное. Удобно, можно передавать его другим. - launch.json
Настройка логики и параметров запуска отладки проекта. Может содержать несколько вариантов настройки для одного и того же проекта. - settings.json
Может содержать настройки плагинов, путей к интерпретатору и тому подобное. Переопределяет настройки из %name%.code-workspace. - devcontainer.json
Файл содержащие настройки процесса сборки контейнера и его содержимого
Что нужно для старта?
В целом все просто — надо поставить сам python, поставить нужные расширения и сделать настройки. Кажется, что все банально, но как обычно «дьявол кроется в деталях».
Настройка Python
Для того, чтобы начать разрабатывать на питоне с помощью VS Code надо сделать 3 вещи:
- Установить сами python (думаю с этим вы сами справитесь).
- Поставить набор плагинов для разработки (делается в самом VS Code, занимает минимум времени).
- Настроить различные дополнительные модули для более комфортной и качественной работы. Это:
- linter (я использую flake8).
- language server (настраивается в файле settings.json, я использую Pylance) (Вот тут неплохая статья описывающая что это и зачем это). Тут как раз обычно и скрыта вся магия.
- параметры запуска отладки (файл launch.json). Вторая часть магии.
Плагины, необходимых для работы с python-ом:
- Python Extension Pack (id — donjayamanne.python-extension-pack)
В этот плагин входит все, что нужно для разработки на python - Dev Containers (id — ms-vscode-remote.remote-containers)
Этот плагин нужен для работы в контейнерах
Настройка удаленной разработки
Об этом я бы хотел тоже упомянуть, так как это очень удобная история. Есть такой набор плагинов от компании Microsoft — Remote Development (id — ms-vscode-remote.vscode-remote-extensionpack). Туда входят:
- Remote — Tunnels
Назначение этого плагина не знаю, не разбирался - Dev Containers
Этот плагин позволяем нам разрабатывать и/или отлаживать код в контейнерах. Обязателен для установки, если хотите работать в контейнерах. - Remote — SSH
Этот плагин предназначен для удаленной разработки с подключением к удаленному хосту по ssh. Причем вы можете не только редактировать файлы или запускать команды, но и заниматься полноценной разработкой с отладкой и всем прочим. Проблема с сохранением файлов, на которых нет прав, может решить плагин sudo (Save as Root in Remote — SSH, id yy0931.save-as-root). - WSL
Этот плагин очень поможет пользователям Windows — с его помощью можно разрабатывать в среде полноценного Linux, подключаясь к нему через WSL.
Все было очень просто, пока репозиторий был один. Кое-как я настроил какие-то совсем базовые вещи и на этом успокоился. Проблемы этого подхода всплыли тогда, когда репозиториев стало три и в одном из них использовался не python, а Go. Ну и конечно же по классике — я не использовал venv =)
Вот небольшой список тех проблем, которые я испытывал постоянно:
- Периодически отваливались импорты в python. Лечилось это переустановкой пакета и перезапуском приложения. (Да-да, это все еще было терпимо, так как происходило не часто)
- Запуск проектов был не самым простым делом, но я как настоящий самурай написал bash-скрипты для запуска. (Чего только не сделаешь, лишь бы не читать доки)
- Когда появился Go — на нем просто не работала отладка… В итоге отладкой я занимался в виртуалке…
Ну и добил меня случай, который я описал выше — я профукал настройки Workspace по дефолту.
И вот, мой дорогой читатель, сижу я перед ноутом, смотрю в пустую страницу VS Code и понимаю, что час настал…
Как я уже говорил, Workspace в VS Code это по сути свое область видимости настроек. Осознав это, я с ужасом понял, что мою идеологию «одно окно — много проектов» можно похоронить. Но остатки надежды говорили, что я не один такой и возможно решение есть среди тысяч плагинов, которые есть для VS Code. Но все оказалось даже лучше, начиная с версии 1.18 этот функционал доступен из коробки. И тут у меня начал вырисовываться примерный план приведения в порядок моего рабочего места разработчика. Он был в целом прост:
- Сделать отдельную корневую папку для всех проектов.
- Каждый проект лежит в отдельной директории.
- Для каждого из проектов я сделаю свои отдельные настройки.
- Все общие настройки буду хранить в корневой общей папке.
Сказано — сделано. В итоге у меня появился такая структура каталогов:
Development
├───.vscode
│ └───my_dev_env.code-workspace
├───First project
│ ├───!env
│ │ ├───prod.evn
│ │ └───dev.env
│ ├───.vscode
│ │ ├───launch.json
│ │ └───settings.json
│ └───app
│ ├───file111.txt
│ ├───file112.txt
│ └───...
├───Second project
│ ├───!env
│ │ ├───prod.evn
│ │ └───dev.env
│ ├───.vscode
│ │ ├───launch.json
│ │ └───settings.json
│ └───app
│ ├───file111.txt
│ ├───file112.txt
│ └───...
└───Third project
├───!env
│ ├───prod.evn
│ └───dev.env
├───.vscode
│ ├───launch.json
│ └───settings.json
└───app
├───file111.txt
├───file112.txt
└───...
Начнем по порядку:
Development/.vscode/my_dev_env.code-workspace.
тут лежат пути к папкам и общие настройки для всех Workspace. В этом файле мы определяем общие настройки на все проекты. Переопределить их можно с помощью локального файла с настройками — settings.json, он будет описан ниже. Пример файла (описание всех свойств даны в самом файле):{ // Список папок, которые должны попасть в вашу multi-root. // Пути можно использовать абсолютные, поэтому можно собирать // воедино папки из разных мест "folders": [ { "path": "/Development/First project" }, { "path": "/Development/Second project" }, { "path": "/Development/Third project" } ], // Общие настройки. Они являются дефолтными для всех включенных директорий, // но могут переопределяться на уровне конкретной корневой папки проекта. // Тут собраны те, что использую я "settings": { "git.autofetch": true, // Включить или выключить периодический поиск изменений в удаленном // репозитории. В случае, если включено в VS Code в разделе работы // с git будет показывать сколько неполученных коммитов есть // в удаленном репозитории. "python.languageServer": "Pylance", // Выбор language server. У Python их несколько, с последнего времени // по дефолту используется Pylance. На мой взгляд лучше оставить его, // так как его делает Microsoft и он активно развивается. "python.defaultInterpreterPath": "/opt/homebrew/bin/python3.10", // Дефолтный путь для Питона "python.analysis.diagnosticMode": "openFilesOnly", // Выбор области, где производится анализ файлов на ошибки. // Я выбрал вариант "только открытые файлы" чтобы меньше логало "python.analysis.autoImportCompletions": true, // Включает автоматическое добавление импорта модуля, если его нет, // но в коде найдены на него ссылки "python.analysis.typeCheckingMode": "off", // Очень полезная, но очень суровая опция - включает проверку // соответствия типов для языкового движка Pylance. По сути попытка // сделать из Python типизированный язык. Доступные значения: // off: анализ проверки типа не проводится; производится диагностика // неразрешенных импортов/переменных // basic: Правила, не связанные с проверкой типов (все правила в off) // + базовые правила проверки типов // strict: все правила проверки типов с наивысшей серьезностью ошибки // (включая все правила в категориях off и basic) "python.analysis.inlayHints.variableTypes": true, // Анализирует ваши переменные и предлагает для них подходящие типы. // Так же позволяет двойным кликом добавить тип "python.analysis.inlayHints.functionReturnTypes": true, // Анализирует ваши функции и классы и предлагает подходящие типы // выходных данных. Так же дает подсказки, какой выходной тип вы получите "python.terminal.activateEnvironment": true, // Если в проекте найдено виртуальное окружение, то в терминале оно // будет автоматически активироваться при переходе в этот проект "python.linting.enabled": true, // Включает линтер для питона "python.linting.flake8Enabled": true, // Выбор каким линтером пользоваться. Я выбрал flake8. "python.linting.flake8Args": [ "--max-line-length=250", // "--ignore=E402,F841,F401,E302,E305", ], // Настройки flake8. Я лично выставил себе только увеличение максимальной // длинны строки - по дефолту 80, этого мало для меня "[python]": { // Настройка автоматического форматирования. Удобно тем, что при сохранении // автоматически приводит форматирование к правильному по мнению форматора виду "editor.defaultFormatter":"ms-python.python", // Выбор форматера "editor.formatOnSave": true, // Включает форматирование при сохранении "editor.codeActionsOnSave": { "source.organizeImports": true // Форматирует импорты. Могут быть проблемы, если импорты зависят друг от друга }, "files.exclude": { // Крайне полезная на мой взгляд функция - задается список файлов, которые // исключаются из показа в дереве каталогов. Отлично подходит для скрытия всяких // ненужных системных каталогов "**/.git": true, "**/__pycache__": true, "**/.DS_Store": true, "**/Thumbs.db": true }, "files.watcherExclude": { // еще одна крайне любопытная опция - за изменениями файлов из // этого списка VS Code не следит "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, "**/node_modules/*/**": true, "**/.hg/store/**": true, "**/__pycache__/**": true, "**/.venv-*/**": true }, "files.enableTrash": false, // Включение/выключение корзины. Если False то файлы удаляются сразу же "cSpell.language": "en,ru", // Крайне полезный плагин и настройки - проверка орфографии. // Настоятельно рекомендую к установке "cSpell.words": [ // Тут список слов, которые мы добавили в исключения "Clickhouse", "fastapi", "jsonify", "loguru" ] } }
Далее идет папка .vscode внутри папки проекта. Тут мы видим два файла — settings.json и launch.json.
Файл settings.json нужен для того, чтобы переопределить параметры из одноименной секции файла workspace (my_dev_env.code-workspace). Вот пример файла{ "python.defaultInterpreterPath": "${workspaceFolder}/.venv-first-project/bin/python3.10", // Переопределяем путь к Python. Тут есть два интересных момента: // 1. используется переменная ${workspaceFolder} - она обозначает // корневую папку проекта // 2. Путь указывает сразу же в каталог с виртуальным окружением "python.envFile": "${workspaceFolder}/!env/dev.env", // Крайне полезная директива - она позволяет задать переменные окружения, // которые будут использоваться при запуске Python. Очень помогает при отладке // при pytest, которым нужны переменные из окружения для работы }
Файл launch.json нужен для настройки запуска отладки приложения. Оооо… Насколько же моя жизнь стала проще, когда я открыл для себя этот файл…
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ // Важно - можно делать сколько угодно наборов настроек отладки { "name": "First project - Dev", // Наименование набора настроек для запуска и отладки. Выводится в // выпадающем списке при запуске отладки. Мне нравится сюда вписывать проект // и окружение, в котором будет запущена отладка "type": "python", // указание какой язык используется - нужно для запуска нужного // набора параметров отладки "request": "launch", // Указывает режим, в котором следует начать отладку. // - launch - если вы запускаете код локально // - attach - если вы используете удаленную отладку // (но эта история более сложная, поэтому тут я ее не буду описывать) "program": "${file}", // Путь к исполняемому файлу. По умолчанию ${file} обозначает файл, // который выбран сейчас в активном окне. Если прописать туда какой-то // конкретный файл, то запускается будет всегда он, вне зависимости // от того, какое окно у вас сейчас выбрано. "console": "integratedTerminal", // Указатель того, куда выводить stdout и stderr. // По умолчанию - встроенный терминал. // Менять в моем понимании надо только если ты четко понимаешь что делаешь ) "python": "${workspaceFolder}/.venv/bin/python3.10", // путь к интерпретатору, с помощью которого запускаться проект "cwd": "${workspaceFolder}", // Указывает каталог, из которого запускается для отладчика, // который является базовой папкой для любых относительных путей, // используемых в коде. Если опущено, по умолчанию используется ${workspaceFolder}. "jinja": true, // Активирует специфические параметры окружения для отладки шаблонов jinja "envFile": "${workspaceFolder}/!env/dev.env", // указывает на файл со списком переменных окружения. // Есть нюанс - на данный момент не поддерживает многострочные переменные, // то есть если вам нужно в окружение добавить большой json - придется // сначала перевести его в компактный вид (в одну строку) "justMyCode": true, // Ограничивает отладку вашим кодом. В случае, если стоит False то отладчик будет // показывать шаги и в стандартных библиотеках. "presentation": { // Настройки видимости этой настройки отладки в списке всех отладок "hidden": false, // Если True то эта отладка не будет видна в списках "group": "api", // Имя группы, нужно для логической группировки отладок "order": 1 // Порядковый номер в группе }, "autoReload": { // Перезапуск отладки в случае сохранения файла с новым кодом. // Если отладка идет в рамках активного файла - перезапустится только он. "enable": true } }, // Пример настройки для запуска проекта написанного на FastApi { "name": "FastAPI", "type": "python", "request": "launch", "module": "uvicorn", // В отличие от предыдущего раза, где запускается файл из текущего активного окна, // тут запускается вполне конкретный модуль "console": "integratedTerminal", "envFile": "${workspaceFolder}/!env/stage.env", // Заведя несколько файлов с переменными окружения можно легко переключаться // на отладку в разных окружениях "python": "${workspaceFolder}/.venv/bin/python3.10", "cwd": "${workspaceFolder}", "args": [ // параметры запуска - специфичны для FastApi "app.main:app", // в моем случае: // первое app- это пака в корне файла проекта // main - это название файла, // второе app - это имя экземпляра класса FastAPI // (выглядит как app = FastAPI()) "--host", "0.0.0.0" // , "--workers", "5" // количество воркеров запускаемых по умолчанию. // Нужно для высоконагруженного продакшена ], "jinja": true, "justMyCode": true, "presentation": { "hidden": false, "group": "api", "order": 5 } } ] }
В итоге, через 3 часа мучений все мои проекты были снабжены нужными им параметрами для запуска отладки, сделаны для всех свои виртуальные окружения и вообще наступила благодать. Но ненадолго…
И вот, казалось бы, наступило счастье, благодать, бабочки порхают и в мире больше не осталось проблем… Но радость была не долгой — мне пришлось работать с MSSQL, а с драйвер упорно не хотел становиться ко мне на машину. И тут я понял, что это следующий пинок — разобраться с возможностью разработки в контейнерах.
История с разработкой в контейнерах прямо внутри VS Code витала в моей голове уже очень давно, но как обычно, волшебного пинка не было. И вот он настал…
На самом деле у VS Code есть два режима поддержки контейнеров:
- полноценная разработка в контейнерах (будет рассматриваться в статье)
- отладка в контейнерах (мне показалась эта история более громоздкой, поэтому не ковырял)
Теперь пара слов о том, как идеологически устроено использование контейнера в качестве полноценной среды разработки.
- В гостевой операционной системе открывается интерфейс.
- Запускается контейнер, в который устанавливаются расширения и специальный сервер, который позволяет интерфейсной части взаимодействовать с контейнером по сети.
- В контейнер монтируется выбранная папка, то есть любые изменения файлов внутри контейнера происходят с файлами, лежащими на файловой системе вашей операционной системы.
- Все процессы, связанные работой с кодом, форматированием и отладкой запускаются внутри контейнера.
Шаги, которые происходят при запуске разработки в контейнере:
- Мы выбираем папку, которую хотим открыть в контейнере.
- VS Code ищет в корне этой папки специальную директорию .devcontainer.
- В этой директории ищется специальный файл — devcontainer.json. Он содержит в себе информацию о том, как запустить нужный контейнер.
- В случае, если мы собираем контейнер сами, то запускается процесс сборки с помощью Dockerfile из этой же директории.
- В случае, если мы пользуемся уже собранным контейнером из заданного регестри, то скачивается указанный нами образ.
- После того, как контейнер запустился, стартует команда, описанная в разделе «initializeCommand» из файла devcontainer.json (если они заданы).
- Следующим шагом запускается установка расширений внутрь контейнера. Список расширений указывается в файле devcontainer.json.
- После установки расширений стартует команда, описанная в разделе «postCreateCommand» из файла devcontainer.json (если они заданы).
Вот так будет выглядеть наша структура каталогов для разработки в контейнерах:
Development
├───.vscode
│ └───my_dev_env.code-workspace
├───First project
│ ├───!env
│ │ ├───prod.evn
│ │ └───dev.env
│ ├───.devcontainer
│ │ ├───devcontainer.json
│ │ └───Dockerfile
│ ├───.vscode
│ │ ├───launch.json
│ │ └───settings.json
│ └───app
│ ├───file111.txt
│ ├───file112.txt
│ └───...
├───Second project
│ ├───!env
│ │ ├───prod.evn
│ │ └───dev.env
│ ├───.devcontainer
│ │ ├───devcontainer.json
│ │ └───Dockerfile
│ ├───.vscode
│ │ ├───launch.json
│ │ └───settings.json
│ └───app
│ ├───file111.txt
│ ├───file112.txt
│ └───...
└───Third project
├───!env
│ ├───prod.evn
│ └───dev.env
├───.devcontainer
│ ├───devcontainer.json
│ └───Dockerfile
├───.vscode
│ ├───launch.json
│ └───settings.json
└───app
├───file111.txt
├───file112.txt
└───...
Рассмотрим более подробно новые файлы.
devcontainer.json:
{
"name": "docker-my_project",
// Это имя будет показываться в интерфейсе VS Code и обозначать в каком
// контейнере вы работаете
"build": {
// Эта секция отвечает за то, как будет собираться ваш контейнер.
// Вариантов два - или собирать из Dockerfile при старте, или брать из
// какого-нибудь registry. Я пошел по первому варианту - он для меня проще
"dockerfile": "Dockerfile",
// Это самый обычный Dockerfile для сборки образа
"context": ".."
// Это очень важный параметр - он указывает в контексте какой директории
// собирается контейнер и относительно этой директории будут отрабатывать
// все пути в Dockerfile. В примере - это папка проекта First project.
// Соответственно, если у вас в Dockerfile написано "COPY app/requirements.txt ."
// то это значит, что при сборке каталог app будет искаться относительно папки
// First project. Эта возможность позволяет вам очень гибко собирать ваши образы.
},
"remoteUser": "root",
// Пользователь из-под кого запускается все в контейнере. Мне было лень
// заморачиваться и я оставил root.
// Есть еще две крайне любопытные команды - они позволяют вписать набор команд,
// выполняемых перед всеми действиями в контейнере (например, установка расширений)
// и сразу же после инициализации контейнера, перед передачей управления пользователю.
// Тут приведен пример, как можно это использовать - ставить зависимости
// или запускать сборки npm.
// "initializeCommand": "cp ../app/requirements.txt .",
// "postCreateCommand": "pip3 install -r /workspaces/alkir-infra-api/app/requirements.txt",
"extensions": [
// тут мы задаем список расширений VS Code, которые хотим увидеть при разработке в контейнере.
// Тут приведен мой - замените на свой.
"ms-python.python",
"wholroyd.jinja",
"formulahendry.code-runner",
"streetsidesoftware.code-spell-checker",
"VisualStudioExptTeam.vscodeintellicode",
"VisualStudioExptTeam.intellicode-api-usage-examples",
"humao.rest-client",
"streetsidesoftware.code-spell-checker-russian",
"adpyke.vscode-sql-formatter",
"mtxr.sqltools",
"ultram4rine.sqltools-clickhouse-driver",
"DotJoshJohnson.xml",
"redhat.vscode-yaml",
"njpwerner.autodocstring"
],
"customizations": {
// В этой секции вы переопределяете настройки по умолчанию.
// Логичнее всего скопировать сюда информацию из общего файла настроек.
"vscode": {
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python3.10",
"python.linting.enabled": true,
"sql-formatter.uppercase": true,
"python.linting.flake8Enabled": true
}
}
},
"forwardPorts": [
// Список портов, которые будут проброшены из контейнера.
// Нужно для того, чтобы обращаться к своему приложению внутри контейнера по сети.
// Так как у меня проект FastAPI он по умолчанию открывает 8000 порт на прослушивание.
8000
]
}
Пример Dockerfile для работы с Python
FROM python:3.10.8-buster as builder
COPY app/requirements.txt .
RUN set -ex \
&& apt-get update -yqq \
&& ACCEPT_EULA=Y apt-get install --no-install-recommends -yqq \
unixodbc-dev \
libpq-dev \
g++ \
git \
rsync \
freetds-dev \
freetds-bin \
tdsodbc \
&& pip3 install flake8 pylint autopep8 \
&& pip3 install -r requirements.txt
После того, как мы все настроили остается только запустить это все. Для этого надо нажать F1, и в появившейся строке ввода ввести «Open folder in container». После этого выбрать корневую папку проекта. После чего запустится сборка контейнера и папка откроется внутри контейнера.
И еще один момент. Как правильно, если вы хотите перезапустить контейнер или перестроить его у вас выпадает ошибка. Это, как правило, связано с тем, что контейнер не успевает удалиться. Попробуйте нажать retry и как правильно все срабатывает.
Так же, из неописанных возможностей (пока просто не разобрался), есть возможность стартовать зависимые контейнеры при запуске отладки. К примеру, вы разрабатывается фронт, и у вас в контейнерах стартуют бек с апи и субд для бека. Очень удобная штука.
Тут находится официальная документация — https://code.visualstudio.com/docs/
Открытая спецификация о разработке в контейнерах — https://containers.dev/
Поддерживаемые переменные в файлах настройки — https://code.visualstudio.com/docs/editor/variables-reference
Настройки python — https://code.visualstudio.com/docs/python/settings-reference
Перечень переменных в файле отладки — https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes