Repository: best-doctor/guides
Branch: master
Commit: 9fb240926bb3
Files: 21
Total size: 95.3 KB
Directory structure:
gitextract__yh35ou0/
├── .editorconfig
├── .gitignore
├── .mdlrc.rb
├── .travis.yml
├── .vocabulary
├── LICENSE
├── README.md
├── guides/
│ ├── airflow_guide.md
│ ├── api_guide.md
│ ├── commit_messages_guide.md
│ ├── en/
│ │ └── python_styleguide.md
│ ├── frontend-styleguide-luchi.md
│ ├── guide_skill_workflow.md
│ ├── infra_infosec_guide.md
│ ├── opensource_guide.md
│ ├── performance_review_guide.md
│ ├── python_styleguide.md
│ ├── python_test_styleguide.md
│ └── team_guide.md
├── requirements.txt
└── setup.cfg
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
# Inspired by Django .editorconfig file
root = true
[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
[*.md]
max_line_length = 80
# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab
================================================
FILE: .gitignore
================================================
.idea
================================================
FILE: .mdlrc.rb
================================================
all
rule 'MD013', :line_length => 120
rule 'MD024', :allow_different_nesting => true
rule 'MD007', :indent => 4
================================================
FILE: .travis.yml
================================================
language: python
python:
- "3.9"
install:
- gem install chef-utils -v 16.6.14
- gem install mdl
- pip install -r requirements.txt
script:
# speller.yandex.net doesn't work anymore
# - rozental --reorder_vocabulary .
- mdl --style=.mdlrc.rb .
================================================
FILE: .vocabulary
================================================
# наш словарь
датаинженерный
конфигу
крон-джобу
мерджим
отгрумленных
перфоманс-ревью
пул
пул-реквест
пул-реквестами
пул-реквестах
пул-реквестов
пул-реквесты
ревью
сингнатуре
стейте
тикету
фидбек
фикстура
фичафлагах
фичафлагов
фичафлагом
эфстринги
юрлицам
# нормальные слова, на которые ругается Я.Спеллер
вовремя
зелёный
карго-культа
кузницах
находиться
опечаток
постфикс
префиксами
процессору
семантичным
сократит
увеличит
улучшит
форматировании
франшизе
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 BestDoctor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# `bestdoctor.guides`
[](https://travis-ci.org/best-doctor/guides)
Сборник гайдов, которым следует команда разработки BestDoctor.
Будем рады, если они пригодятся и вам.
## Руководства
- [Python styleguide](./guides/python_styleguide.md) ([eng](./guides/en/python_styleguide.md))
- [Frontend Styleguide LUCHI](./guides/frontend-styleguide-luchi.md)
- [Guide-skill workflow](./guides/guide_skill_workflow.md)
- [Python testing styleguide](./guides/python_test_styleguide.md)
- [Team guide](./guides/team_guide.md)
- [Performance review guide](./guides/performance_review_guide.md)
- [API guide](./guides/api_guide.md)
- [Opensource project guide](./guides/opensource_guide.md)
- [Airflow guide](./guides/airflow_guide.md)
- [Infrastructure security guide](./guides/infra_infosec_guide.md)
================================================
FILE: guides/airflow_guide.md
================================================
# Датаинженерный гайд по работе с Airflow
В этом гайде собраны договорённости, который мы соблюдаем при работе
с Apache Airflow.
## Структура кода
### SQL
Весь SQL лежит в папочке `sql`, раскиданный по отдельным файликам.
Один запрос – один файл.
### DAG
Даги сгруппированы по директориям, одна директория – один даг.
Структура внутри директории такая:
- `dag.py` - здесь только DAG, его параметры и операторы;
- `helpers.py`;
- `constants.py`;
- `tests`:
- `test_dag.py`;
- `test_helpers.py`;
- `factories.py`.
### Общий код
Функции и операторы, которые используются в нескольких дагах, выносятся
в папку `utils` и импортируются оттуда.
Структура `utils`:
- `operators`;
- `sensors`;
- `hooks`;
- `utils`.
В названии файлов внутри директорий не дублируем тип сущности.
Так плохо: `sensors/github_sensor.py`, так хорошо: `sensors/github.py`.
## Модели
Некоторые данные мы храним в PostgreSQL и используем SQLAlchemy
для работы с ними.
- Каждая таблица находится в отдельном файле в папке models.
Нейминг файлов по названию таблиц (например `dataset_losses.py`).
-Названия полей – `<наименование таблицы-источника>_<наименование поля>`,
например `patient_last_name`, `clinic_legal_title`.
- В названиях моделей датасетов используем префикс `Dataset`.
- В названиях моделей витрин используем префикс `Datawallmart`.
## Миграции
- Название новой миграции состоит из номера – номера миграций идут подряд,
независимо от изменений.
- В названии после номера указываем суть изменений.
Примеры:
- `1_some_table_create.py`;
- `2_some_other_table_create.py`;
- `3_some_table_add_beauty_field.py`;
- `4_dataset_my_precious_change_field_types.py`.
## Pandas
Мы не используем Pandas. Верим, что стандартной библиотеки достаточно
для написания хорошего, понятного, поддерживаемого и быстрого кода.
Возможно, с разрастанием кодовой базы мы переиграем это правило, но пока так.
## Промежуточные результаты
Промежуточные результаты храним в csv файлах в `/tmp` директории.
Сами файлы именуются по правилу `<dag_id>_<task_id>.csv` и имена
хранятся в строковых литералах, у которых также есть префиксы:
- `extract_` — для файлов, в которые экспортируются
первичные данные;
- `temp_` — для промежуточных файлов после преобразования,
если из них данные не записываются сразу в таблицы;
- `load` — для файлов с данными, которые загружаются
в базу данных.
## Правила написания дагов
- Мы используем контекст менеджеры и не передаем DAG внутрь оператора явно –
чтобы было абсолютно четко понятно из каких операторов состоит DAG.
- `start_date` задается в виде статического `datetime` с обязательным
указанием часов и минут – потому что у Airflow есть проблемы
с динамическими датами при переходе через полночь, а также
чтобы всегда было явно понятно время с которого DAG начинает исполнятся.
- `start_date` должен быть в прошлом всегда и к текущему моменту как
минимум один интервал должен умещаться в этом временном отрезке –
это позволяет избежать ненужных проблем со scheduler.
- `schedule_interval` задается строкой с использованием cron-синтаксиса –
он короче, чем `timedelta`, более гибкий и больше нам нравится.
- Мы не используем пресеты `@hourly`, `@daily` и т.д. – потому что
`explicit is better than implicit` – в кроне мы явно задаем
время исполнения DAG и контролируем это;
- SubDag'и запрещены – под капотом у них SequentialExecutor, который
не годится для бое использования.
- Для определения зависимостей между тасками используем оператор побитового
сдвига `>>` – потому что это наглядно и читается лучше чем
`some_task.set_upstream(some_other_task)`. При этом зависимости
задаются слева направо – потому что мы так читаем текст.
## Обработка данных
- General rule of thumb – если можно выполнить простую операцию на
стороне datasource, мы выполняем ее на стороне datasource.
- Все результирующие датасеты (`dataset_` & `datawalmart_`) должны
содержать поле `last_modified_at` типа `timestamp`.
- Все даты и времена хранятся в UTC.
================================================
FILE: guides/api_guide.md
================================================
# REST API BestDoctor
## Общие принципы построения API
1. API следует принципу HATEOAS, генерируя необходимые ссылки на ресурсы. Клиент опирается на доступные в ответе
ресурсы, а не на хардок ссылок API.
1. API возвращает использует `HTTP` статусы в ответах. Успешная обработка запроса - `20x`, редирект - `30x`, ошибка в
запросе или при его обработке - 4xx.
1. API информативен. Для успешной обработки запроса помимо ответа API может вернуть параметры запроса. В случае ошибки
возвращается описание и массив ошибок валидатора, может быть ссылка на документацию по API и рекомендации как ошибки
избежать.
1. Работать с API будут не столько машины, сколько разработчики, и мы хотим максимально им в этом помочь.
1. API использует стандартные HTTP-заголовки. Версионирование, запрос конкретных feature, желаемый типа данных, передача
некоторых параметров типа TimeZone - все идет в заголовках и не замусоривает тело запроса повторяющимися параметрами.
1. API быстрый. Ответ должен генерироваться максимум за 250мс, а тяжелые операции должны проводиться асинхронно.
1. API это не (только лишь) CRUD над ORM, сущности API должны отражать бизнес-процессы. В дополнение к этому может
существовать и низкоуровневый слой API для манипуляции с базой.
### Формат запроса и ответа
Все запросы и ответы передаются в формате json. Ответ содержит поле `data` в случае успешной обработки (код `200 OK`,
`201 Created`), может не содержать тела (код `204 No Content`).
```http request
GET /patients/me HTTP/1.1
Accept: application/vnd.bestdoctor.v1.verbose+json
HTTP/1.1 200 OK
{
"data": {
"url": "/patients/51413",
"patient_id": 51413,
"name": "John",
"surname": "Doe"
},
"meta": null,
"query": {"patient_id": "me"}
}
```
Кроме поля data ответ может содержать следующие поля:
* `meta` - дополнительная информация по запросу, например, размер выборки для коллекций
* `query` - разобранный запрос, включая дефолтные значения необязательных полей, не заданных в запросе. Отражает полные
входные данные обработчика запроса.
Каждый объект содержит ссылку на себя в атрибуте `url` и ссылки на связанные ресурсы в атрибутах, имеющих суффикс `_url`.
В случае ошибки ответ содержит поле `message` с человекочитаемым описанием проблемы, массив `errors` может содержать
подробности от валидаторов.
```http request
POST /patients/me HTTP/1.1
name=JOHN
HTTP/1.1 401 Authorization Required
{"message": "You should be logged in"}
```
Коды ответа `200`, `201`, `204` возвращаются для успешно обработанных запросов. При ошибках обработки 400, ошибках
приложения `420`, ошибках валидации `422`, ошибках доступа `401`, `403`, `404` (когда необходимо).
### Передача параметров
Параметры для GET-запросов передаются в url и в query string после `?`. Остальные запросы принимают параметры в строке
адреса и в теле запроса.
```http request
GET /patients?page=1 HTTP/1.1
```
```http request
POST /patients/51413 HTTP/1.1
{"name": "Vasiliy"}
```
### Ошибки обработки запроса
В зависимости от схемы ответ с сообщением об ошибке может содержать поля:
* `message` - человекочитаемое сообщение об ошибке (для разработчика или пользователя клиентского приложения)
* `int_code` - числовой код ошибки для автоматической обработки клиентскими приложениями
* `suppress_message` - флаг: можно ли не показывать сообщение пользователю клиентского приложения
* `errors` - массив ошибок с указанием:
* `resource` - ресурса
* `field` - поля запроса
* `code` - буквенно-цифрового кода ошибки
* `message` - сообщения пользователю
### Ошибка `400 Bad Request`
При ошибке разбора запроса (передан невалидный json) возвращается `400 Bad Request`
```http request
PATCH /patients/me HTTP/1.1
Host: api.bestdoctor.ru
Authorization: Token hexdigest-token-string
name=JOHN
HTTP/1.1 400 Bad Request
{"message": "Body should be a valid json"}
```
### Ошибка `420 Going To Hell Error`
Все ошибки, возникающие при обработке валидного запроса, возвращаются с кодом `420 Going To Hell Error`. Что-то пошло не
так с точки зрения сервера, но это не фатальная ошибка сервера типа `500`, а ошибка, предусмотренная бизнес-логикой.
```http request
POST /patients/me/visits HTTP/1.1
Authorization: Token hexdigest-token-string
{"clinic_id": 54321, "timeslot": "2020-04-15T10:45:00+03:00"}
HTTP/1.1 420 Going To Hell Error
{
"message": "Выбранное время для записи в клинику недоступно",
"int_code": 90210,
"suppress_message": false,
"errors": [
{
"resource": "Clinic",
"code": "timeslot_is_busy"
}
]
}
```
### Ошибка валидации запроса `422 Unprocessable Entity`
При ошибке валидации запроса - `422 Unprocessable Entity` + массив `errors` с описанием ошибок валидации.
```http request
PATCH /patients/me HTTP/1.1
Host: api.bestdoctor.ru
Authorization: Token hexdigest-token-string
{"patient_id": 12345, "phone": "сотовый"}
HTTP/1.1 422 Unprocessable Entity
{
"message": "Validation error",
"errors": [
{
"resource": "Patient",
"field": "patient_id",
"code": "readonly_field",
"message": "Id пациента нельзя редактировать"
},
{
"resource": "Patient",
"field": "phone",
"code": "wrong_phone_format",
"message": "Телефон должен содержать хотя бы одну цифру"
}
]
}
```
### Аутентификация клиента
Данные для аутентификации клиента передаются в заголовке вида `Authorization: (basic|bearer|token) <value>`
1. Basic-схема может использоваться в обычном режиме (login:password) для авторизации некоторых приватных методов API,
как базово поддерживаемая в url схема
1. Basic-схема может использоваться в режиме login:OAuth для работы с OAuth и разделением прав разных приложений
(сайт, мобилка, премиальная мобилка)
1. Bearer-схема может использоваться с сессионным токеном JWT
1. Token-схема может использоваться для авторизации приложений по OAuth
Отсутствие заголовка аутентификации может повлечь одно из следующего:
* Успешный ответ, но явную или неявную фильтрацию/ограниченный объем ответа (к примеру, список публично доступных
ресурсов может быть меньше, чем для пациента или HR).
* Ошибку `401 Authorization Required`
При успешной аутентификации клиента, но нехватке прав на конкретный запрашиваемый ресурс, API обычно отвечает `403 Not
Authorized`. В некоторых случаях стоит возвращать `404 Not Found,` чтобы скрыть сам факт наличия конкретного ресурса
(клиенту, не имеющему должных прав, обращения к `/clients/nonexistent` и `/client/existing` должны оба вернуть `404`).
### Паджинация (страничный вывод)
Максимальный размер страницы данных, которую возвращает API за один запрос, ограничен сервером, и может указываться в
параметре запроса `per_page` (дефолт 20, максимум 200). В ответе в разделе `meta` содержатся ссылки `url`, `next_url`,
`prev_url` и `last_url` на текущую, следующую, предыдущую и последнюю страницы набора данных. Поле `page_tmpl_url`
содержит шаблон для формирования url страницы по её номеру.
Чтобы скачать полный набор данных, клиенту следует запрашивать следующую страницу по адресу из `meta.next_url`, пока это
поле присутствует в ответе
В разделе `meta` может присутствовать поле `total`, обозначающие общее количество элементов в наборе, и поле `pages`,
обозначающее общее количество страниц (размера `per_page`) в наборе.
#### Постраничная паджинация
Страница выбирается параметром `page` (1, 2, 3, ...).
```http request
GET /patients?per_page=10 HTTP/1.1
Host: api.bestdoctor.ru
Accept: application/vnd.bestdoctor.v1.verbose+json
HTTP/1.1 200 OK
{
"data": [{"url": "patient_id": 51413, "name": "John", "surname": "Doe"}, ...],
"query": {"page": 1, "per_page": 10},
"meta": {
"total": 98, "pages": 10,
"page_tmpl_url": "/patients?page={page}&per_page=10",
"url": "/patients?page=1&per_page=10",
"next_url": "/patients?page=2&per_page=10",
"last_url": "/patients?page=10&per_page=10"
}
}
```
#### Относительная паджинация
Некоторые ресурсы, например отсортированные по времени создания по убыванию, неудобно запрашивать по номеру страницы.
Так `page=1` может всегда указывать на самые последние записи, однако два запроса одной и той же страницы могут вернуть
разный набор данных, если он успел измениться.
Параметр `before=[object_uuid]` или `after=[object_uuid]` задаёт две вещи: граничный элемент и направление сортировки
результата. Так, вариант с `before` запрашивает все элементы с датой создания строго меньше, чем у граничного, и
отсортированные по убыванию этой даты, а вариант с `after` - все элементы с датой создания строго больше, чем у
граничного, и отсортированные по возрастанию этой даты.
```http request
GET /chat/messages?per_page=10&before= HTTP/1.1
Host: api.bestdoctor.ru
Accept: application/vnd.bestdoctor.v1.verbose+json
HTTP/1.1 200 OK
{
"data": [{"uuid": "UUID_FIRST", ...}, ..., {"uuid": "UUID_LAST", ...}],
"meta": {
"total": 98,
"url": "/chat/messages?per_page=10&before=UUID_FIRST",
"next_url": "/chat/messages?per_page=10&before=UUID_LAST",
"prev_url": "/chat/messages?per_page=10&after=UUID_FIRST"
}
}
```
Если `object_uuid` не задан, то выдается страница отсортированной коллекции и в `meta.url` указывается запрос
относительно первого элемента выборки.
### Версионирование API
Заголовок запроса `Accept` отвечает за выбор версии API, формата ответа, дополнительных опций API.
Базовый content-type для API это `application/json` - формат ответа json, кодировка UTF-8, все дополнительные опции и
версия API в дефолте.
Расширенный content-type выглядит так: `application/vnd.bestdoctor.[version].[option[-verbose]][+format]`
Параметр `format` всегда равен `json`.
Option позволяет выбрать различные варианты формирования ответа, передается в конкретный обработчик и может
интерпретироваться по-разному:
* `compact` - например, не подставлять связанные объекты, а оставлять их ссылками, либо не включать в выдачу
дополнительные параметры
* `full` - включать в ответ все связанные объекты, дополнительные параметры
* `verbose` - включать в ответ исходный query или расширенную информацию в meta, красиво форматировать json для
разработчика.
## Ресурсы
### Пациенты
#### Список разрешений
* `patient:view` - просмотр информации о пациенте
* …
#### Получить список пациентов
`GET /patients` - список пациентов, pageable
Параметры:
* `page`, `per_page` - настройки паджинации
* `qs` - строка поиска
Формат ответа:
```text
{
"data": [
{
"url": url пациента,
"patient_id": id пациента, обязательное поле,
…
"appeals_url": url списка обращений,
"permissions": ["patient:view", "appeal:add_for_patient"]
},
…
]
}
```
#### Создать пациента
`POST /patients`
Параметры: ...
#### Получить детальную информацию о пациенте
`GET /patients/:patient_id`
Формат ответа:
```text
{
"data": {
"url": url пациента,
"patient_id": id пациента, обязательное поле,
…
"program": {
"url": url на программу, может отсутствовать,
…
},
"slot": {
"url": url на слот, может отсутствовать,
…
},
"appeals_url": "/appeals/?patient=uuid" - url списка обращений,
"permissions": ["patient:view", "appeal:add_for_patient"]
},
}
```
#### Изменить информацию о пациенте
`PATCH /patients/:patient_id`
#### Удалить пациента
`DELETE /patients/:patient_id`
#### Создать обращение (связанный объект) по пациенту
`POST /patients/:patient_id/appeals`
#### Получить список обращений по пациенту
`GET /patients/:patient_id/appeals`
### Обращения
#### Список разрешений
* `appeal:add_for_patient` - создание обращения для конкретного пациента
* `appeal:add` - создание обращения
#### Получить список пациентов
`GET /appeals` - список пациентов, pageable
Параметры:
* `page`, `per_page` - настройки паджинации
* `qs` - строка поиска
* `patient` - uuid пациента для фильтрации
#### Создать обращение
`POST /appeals`
#### Получить детальную информацию по обращению
`GET /appeals/:appeal_id`
#### Обновить статус обращения
`PUT /appeals/:appeal_id/resolution`
#### Удалить статус обращения
`DELETE /appeals/:appeal_id/resolution`
#### Привязать обращение к пациенту
`PUT /appeals/:appeal_id/patient`
#### Получить список возможных результатов обращений
`GET /appeals_results`
Формат ответа:
```text
{
"data": [
{
"id": 4, // id результата
"text": "наименование"
},
]
}
```
#### Получить список категорий обращений
`GET /appeals_categories`
Формат ответа:
```text
{
"data": [
{
"id": 12, // id категории
"parent": 4, // nullable ссылка на родительскую категорию,
"text": "Жалобы"
},
]
}
```
================================================
FILE: guides/commit_messages_guide.md
================================================
# Сообщения коммитов
Сообщение коммита – невероятно важная вещь, так как зачастую это единственное место,
где можно найти контекст, в котором были произведены изменения кода.
---
## Писать сообщения на английском языке
Если нет уверенности в своих способностях, можно выразиться проще или воспользоваться переводчиком.
### Зачем?
- Чтобы соблюдать правило хорошего тона
- Чтобы не скатываться к перемешиванию непонятных англицизмов с русским языком
- Английский язык — фактически язык любого разработчика. Профессия обязывает его знать.
### Хорошо
```
3cf33864e2 add tests
136e39ff28 add base logic
```
### Плохо
```
3cf33864e2 добавить тесты
136e39ff28 негізгі логиканы қосты
```
---
## Начинать сообщение с заглавной буквы
### Зачем?
- Чтобы текст был читабельнее
- Грамматику никто не отменял. Это мелочь, но внимание к мелочам приближает нас
к совершенству, а не отдаляет от него. Правила стоит нарушать, если от этого есть толк.
### Хорошо
```
0bb3518daf BTB-288: Add basic authorization
```
### Плохо
```
0bb3518daf BTB-288: add basic authorization
```
---
## Использовать глаголы в повелительном наклонении
Повелительное наклонение означает «устное или письменное распоряжение, команда
или инструкция». Например: _add_, _update_, _refactor_ и т.д.
Есть простое правило для написания сообщений в этом стиле:
Правильно сформированная тема коммита Git должна всегда иметь возможность завершить следующее предложение:
If applied, this commit will <your message>
### Зачем?
- Сам git использует такой формат, например, когда создает коммиты слияния
- Коммиты можно рассматривать как команды
(например, вид лога и команда revert начинают играть новыми красками)
### Хорошо
```
3cf33864e2 BTB-501: Add integration tests for certificates
af932688a0 BTB-501: Attach certificate logic to the registry import order handler
0bb3518daf BTB-501: Add facade to mark certificate as ready for activation
```
### Плохо
```
3cf33864e2 BTB-501: Integration tests for certificates
af932688a0 BTB-501: Attaching certificate logic
0bb3518daf BTB-501: Added facade to mark certificate as ready for activation
```
---
## Связывать коммит с задачей
Обязательно использовать формат сообщения `<ticket>: <message>`.
Во избежание нарушения этого правила рекомендуется настроить прекоммит-хук,
который проверяет сообщение коммита.
Иногда бывают ситуации, когда нужно закоммитить код без привязки к задаче,
например, хотфикс или выпил флага (хотя, в этих случаях тоже рекомендуется заводить задачи).
Тогда можно использовать заглушку с числом `100500` вместо номера тикета.
Если тикеты привязаны к командам или проектам (в случае `Jira` или `Яндекс.Трекер`),
то следует выбирать очередь своей команды.
Если же это вообще не имеет никакого отношения к продукту,
можно использовать название инжиниринговой очереди (в случае нашей компании `BANG`).
К тому же, GitLab умеют превращать номер задачи в коммите в ссылку на задачу в трекере.
### Зачем?
Ситуация: разработчик приходит исправлять ошибку, находит подозрительное место,
а сообщение коммита, в рамках которого была исправлена строка, выглядит как `add new selector`.
Это, конечно, фиаско, и в этом случае единственная надежда на
хорошее описание задачи в трекере задач, по которому можно будет понять,
почему был написан этот код и какая должна быть логика на самом деле.
Это особенно актуально в нашем случае, когда автоматизация код-ревью
заточена под номер задачи.
_Для пользователей PyCharm_: существует способ настроить встроенную консоль гита
так, чтобы номер тикета сразу оформлялся как ссылка на трекер.
### Хорошо
```
0bb3518daf BTB-100500: Refactor slot__detach interactor
```
### Плохо
```
af932688a0 Add Phone Number Pydantic type, using google's phonenumbers
0bb3518daf NOISSUE-100500: Refactor slot__detach interactor function
```
---
## Сначала надо ответить на вопрос «Зачем?»
В первую очередь при написании сообщения коммита стоит задуматься о том,
зачем вообще был сделан этот коммит? А ответ записать в сообщение.
### Зачем?
По диффам всегда видно, что поменялось.
А вот зачем были внесены изменения, порой решительно непонятно.
Однажды придется исправлять этот код, и можно сэкономить много времени в будущем,
если написать правильное сообщение сейчас.
### Хорошо
```
af932688a0 BTB-501: Attach certificate logic to the registry import order handler
0bb3518daf BTB-501: Add facade to mark certificate as ready for activation
cdb4629fqc BTB-501: Fix bug when phone number saves to database incorrectly
```
### Плохо
```
af932688a0 BTB-501: Add new logic
0bb3518daf BTB-501: Add facade
```
---
## Не стоит смешивать изменения
Это в большей степени относится к культуре создания коммитов,
но сообщения являются отличным маркером.
Если не получается и емко описать, что происходит в этом коммите
(или еще хуже, приходится в теле сообщения написать список изменений),
стоит декомпозировать изменения и закоммитить код
в нескольких последовательных коммитах вместо одного большого.
Иногда лучше сделать 10 маленьких коммитов, чем один большой,
в котором собрано все, что связано с задачей.
**Главное правило**: не коммитить изменения бизнес-логики при написании тестов.
Бывает, что в разных коммитах добавляются модели, потом логика, потом тесты.
Хочется избежать ситуаций, когда в такие коммиты подмешиваются изменения бизнес-логики.
При нахождении бага лучше зафиксировать изменения отдельным коммитом, а потом вернуться к тестам.
Однако, если коммит содержит большой объем кода,
полноценно закрывающий функциональность (т.е. это вся бизнес-логика и тесты, например),
допустимо закоммитить все вместе.
Но тут срабатывает правило «попробуй кратко и емко описать, что происходит в этом коммите».
Если кратко описать не получается, вместо того, чтобы писать общее сообщение,
стоит подумать, что в этом коммите самое важное?
После этого подумать, допустимо ли смешивать изменения.
Иногда ответ может быть положительный, иногда отрицательный.
### Зачем?
Чтобы иметь возможность кратко и емко описать, зачем нужен этот коммит.
К тому же, следовать многим другим рекомендациям невозможно без выполнения этого условия.
## Хорошо
```
# закоммитили разные учатки кода в разных коммитах
3cf33864e2 BTB-288: Fix program annotation
programs/facades.py
af932688a0 BTB-288: Add certificate activation via Tilda
certificates/facades.py
0bb3518daf BTB-288: Add unit tests for certificate activation
certificates/tests/test_facades.py
```
или
```
# закоммитили всю фичу целиком
3cf33864e2 BTB-288: Add certificates activation via Tilda
programs/facades.py
certificates/facades.py
certificates/tests/test_facades.py
```
### Плохо
```
# непонятное сообщение, при этом затронуто очень много приложений
3cf33864e2 BTB-288: Add logic
appeals/facades.py
programs/facades.py
certificates/facades.py
certificates/tests/test_facades.py
settings.py
```
или
```
# в сообщении про тесты, но среди файлов бизнес-логика
3cf33864e2 BTB-288: Add integration tests
certificates/facades.py
certificates/tests/test_facades.py
```
---
## Писать осмысленные сообщения
Иногда хочется написать «исправил баг» или «исправления после код-ревью».
Лучше не поддаваться этому желанию, а постараться максимально четко и ясно написать,
что и зачем было сделано.
### Зачем?
Все затем же. Чтобы однажды иметь возможность понять, зачем было сделано то или иное изменение.
К тому же, такой подход заставит задуматься, а надо ли все исправления по код-ревью пихать в один коммит.
Может, не стоит?
### Хорошо
```
3cf33864e2 BTB-501: Fix typo in error message
af932688a0 BTB-501: Fix duplication of requests in annotation
0bb3518daf BTB-501: Add logic for manual certificate activation
```
**Плохо**
```
3cf33864e2 BTB-501: CR fixes
af932688a0 BTB-501: Fix error
0bb3518daf BTB-501: Add logic
```
---
## Сообщения должны занимать одну строку
Не надо писать длинные сообщения на много строк.
Не надо пытаться описать в сообщении всю бизнес-логику или скопировать текст связанной задачи в трекере.
**Важно!** В некоторых случаях допустимо написать в теле большое сообщение,
в котором можно подробно описать проблему и способ её решения, но это бывает редко.
Для полноты контекста обычно достаточно ссылки на тикет и короткого сообщения.
## Зачем?
- (Конкретно в нашем случае) автоматизация код-ревью не заточена под многострочные коммиты
- GitLab не очень хорошо отображает многострочные коммиты и сворачивает их по умолчанию
- Если хочется написать многострочное сообщение, стоит задуматься,
соответствует ли коммит описанным выше рекомендациям.
### Хорошо
```
3cf33864e2 BTB-288: Fix program annotation
```
### Плохо
```
3cf33864e2 BTB-288: Fix programs
- fix selectors
- fix templates
- fix facades
```
---
## Не забывать про amend во время локальной разработки
Опция `--amend` позволяет закоммитить изменения в рамках предыдущего коммита,
с возможностью переопределить сообщение.
**Важно!**
Как только код в общедоступном репозитории, использовать эту опцию нельзя;
`--amend` работает только локально, на коммитах, которые не запушены.
Нарушение этой рекомендации может привести к рассинхронизации ветвей репозитория.
### Зачем?
Чтобы не генерировать мусорные коммиты.
### Хорошо
```
3cf33864e2 BTB-501: Add logic for manual certificate activation
certificates/facades.py
certificates/selectors.py
certificates/interactors.py
```
### Плохо
```
3cf33864e2 BTB-501: Add logic for manual certificate activation
certificates/facades.py
af932688a0 BTB-501: Add logic for manual certificate activation
certificates/selectors.py
certificates/interactors.py
```
---
## Не ставить точку в конце строки
### Зачем?
А зачем ставить? 🙃
### Хорошо
```
3cf33864e2 BTB-288: Fix program annotation
```
### Плохо
```
3cf33864e2 BTB-288: Fix program annotation.
```
---
Полезные статьи:
- [Как писать сообщения коммитов в Git [Medium]](https://medium.com/grisme/как-писать-сообщения-коммитов-в-git-9ed19ebc5ebf) + [оригинал](https://cbea.ms/git-commit/)
- [Как оформлять коммиты, чтобы потом не было больно [Хабр]](https://habr.com/ru/companies/Voximplant/articles/276695/)
================================================
FILE: guides/en/python_styleguide.md
================================================
# BestDoctor Python styleguide
## Goals
We are developing a huge product with tons of functionality. Moreover, we need to maintain and improve our existing codebase.
In order to make this process as painless as possible, the code needs to be
flexible, loosely coupled, comply with DRY and KISS principles...
The list goes on. Otherwise, developers, technical support or the business
itself will get an unpleasant surprise later down the line.
And you want to avoid that.
In order to get the code that meets our standards,
we need to constantly keep our focus. That in itself can be quite challenging
as it requires a lot more awareness than simply writing code.
We made this styleguide, so it can help us keep track of our solutions
to most common problems, instead of having to reinvent the wheel each
time one of those problems comes up.
This guide contains no rules, only guidelines. We follow these
guidelines in order to write amazing code and not just
for the sake of following them.
It is also very important to us that our code satisfies our team.
That's why the guidelines below represent our personal preferences
that may not align with yours.
## You are not required to follow these guidelines
As a matter of fact, in some cases it is better not to follow this styleguide.
There are situations when it is okay to write `import *`, `except Exception`,
use tab indents or write really long lines of code.
You can do that if you are absolutely certain that it will be beneficial
to your codebase. However, those situations are quite rare.
## The latest Python release
Use 3.9 in production. Use 3.9 in staging. Use 3.9 on every instance.
Think in 3.9 as well.
## PEP8
We love and follow PEP8 except for the maximum line length.
We have set our upper-limit to 120 characters.
We use [black](https://github.com/psf/black) with single quotes.
## Comments
### No unnecessary comments
There are rules that require writing comments in certain places,
e.g. public modules, classes, methods.
We don't have such rules. We think that you should only write a comment
when something in your code acts in an unexpected manner.
If you can't figure out the purpose of an entity, then you should
consider renaming it. Maybe it will help clear things up.
### Oneline docstrings
A quick way to write a docstring which can fit in one line. Keep the comment
and the quotation marks on the same line and don't forget a full-stop.
Bad:
```python
"""
Statistics on clients' legal entities.
"""
```
Bad:
```python
"""Statistics on clients' legal entities"""
```
Good:
```python
"""Statistics on clients' legal entities."""
```
### Multiline docstrings
Used when you need to provide a description which exceeds one line.
Here's how it works: the first line should be used to describe
what you are talking about. It ends with a full-stop.
The first line and the description are separated by an empty line.
There should be no spaces between the text and the quotation marks.
Bad:
```python
"""
Active service period.
Functions as inteded when user has one or less slots
"""
```
Bad (missing a full-stop):
```python
"""
Active service period
Functions as inteded when user has one or less slots
"""
```
Good:
```python
"""
Active service period.
Functions as inteded when user has one or less slots
"""
```
### Inline comments
You can and you should use them when you need to describe tricky logic,
refer to a ticket or to a wiki article.
You shouldn't use them to make jokes, to describe a certain part of the code or
to divide it into sections. Leave the first two for the water cooler
conversations and the last one – for code refactoring.
Bad:
```python
# this is shit
```
Bad:
```python
# fetching user data
...
# building a context for the report
...
# generating the report
...
```
Good:
```python
# refer to BES-482 for the formula explanations
```
### TODO and FIXME
We use TODO and FIXME to mark something important when we don't want to get
distracted while writing code. We try to remove them before making a commit.
Instead of keeping those marks, we prefer to either fix those parts
immediately or create a ticket to do it later.
There are two reasons for doing so. One is that those marks can stay
in the code for years and no one does anything about it.
And two is that they make an impression that everything is broken.
Keep your code improvement and refactoring tasks in the task tracker,
not in your code.
### Inline comments and urls
Leaving a link without a note in an inline comment is a bad idea.
It's better to write a description because it will help others get the context
without having to follow the link.
Bad:
```python
# https://stackoverflow.com/q/184618/3694363
```
Good:
```python
# empties the cache properly: https://stackoverflow.com/q/184618/3694363
```
## String literals and formatting
Use single quotes for string literals, but avoid using backslashes in strings.
To format a string, use `.format` or f-strings with indexed or
named placeholders. Do not concatenate strings with `+` operator.
F-strings can be used, but you should avoid putting a lot of logic
inside of them.
Bad:
```python
'%s %s' % (first_name, last_name)
```
Bad:
```python
'{} {}'.format(first_name, last_name)
```
Good:
```python
'{0} {1}'.format(first_name, last_name)
```
## Imports
We use [isort](https://github.com/PyCQA/isort).
Third-party libraries should be imported on a case by case basis,
i.e. import only the functions and classes that you need.
Do not import the entire module and for the love of god do not use `import *`.
Resolve name collisions with `as` statement.
Standard library modules should be imported in their entirety.
Imports should be divided into three sections as described in PEP8.
There are no set rules for sorting the imports inside a section.
Bad:
```python
from collections import Counter
```
Bad:
```python
from core import models
```
Good:
```python
import collections
from core.models import Patient
```
## Code inside `__init__.py`
`__init__.py` should only contain imports and nothing else.
## Calls formatting
Do not use line breaks if a call and all of its' arguments can
fit into a single line.
If one line is not enough, but you can fit it in the next line
after the first bracket, then you should do that.
If there are too many arguments or you can't fit a call into two lines,
then use advanced formatting. In that case, you can place several arguments
in the same line if they have similar meaning. For instance, you can combine
`date_from` and `date_to` or `to` and `on_delete`.
Bad:
```python
recieved_date = models.DateField(
'Receival date', auto_now_add=True,
) # everything fits into a sinlge line, no need to break it
legal = models.ForeignKey(
'bestdoctor.ClinicLegalEntity',
models.CASCADE,
'registries', 'registry',
verbose_name='clinic legal entity',
) # closing paranthesis has excess indent
```
Good:
```python
recieved_date = models.DateField(
'Recieval date', auto_now_add=True)
legal = models.ForeignKey(
'bestdoctor.ClinicLegalEntity',
models.CASCADE,
'registries', 'registry',
verbose_name='clinic legal entity',
)
```
Good:
```python
legal = models.ForeignKey(
'bestdoctor.ClinicLegalEntity',
models.CASCADE,
'registries',
'registry',
verbose_name='clinic legal entity',
)
```
## Function calls and arguments
When calling a function, specify argument names if they are unclear from
the name of the function alone.
You may write them even if they are obvious.
Bad:
```python
serializer.is_valid(True)
```
Good:
```python
serializer.is_valid(raise_exception=True)
```
## Commas
Place a comma after the last item in any kind of iterable.
This includes lists, calls, tuples or anything similar.
If it fits in one line then we don't use a comma.
The only exception is a one element `set`.
Bad (forgot a comma in the end):
```python
list_filter = (
'legal', 'is_finished', 'is_paid', 'client_registry__record__report_date'
)
```
Bad:
```python
Act.objects.create(
date=today(),
act_type='tech',
report_date=report_date,
is_finished=False
)
```
Good:
```python
list_filter = (
'legal', 'is_finished', 'is_paid', 'client_registry__record__report_date',
)
Act.objects.create(
date=today(),
act_type='tech',
report_date=report_date,
is_finished=False,
)
```
## Unused code
We want as few bugs as possible in our code. A great way to get rid of bugs is
to get rid of code. No code means no problems. That's why we remove everything
that can be removed, such as deprecated features, one-time scripts,
pieces of code which have been commented out. All of it gets cut.
It's not difficult to recover it later. Since all commits are tied to a ticket
in the task manager, to find removed code you need to first find the ticket,
then its commits and then reapply them.
If there is something that you don't need this month then cut it.
## Data in settings
We are doing our best to keep all of the data where it belongs.
Project settings is definitely not the right place for it.
Here's an example of how to move a piece of data from the settings to the
database with the help of `BooleanField`:
Before:
```python
# settings.py
NOTIFY_IN_SLACK_COMPANY_IDS = [123, 456, 789]
# code
send_slack_notification(NOTIFY_IN_SLACK_COMPANY_IDS)
```
After:
```python
# Companies now have notify_in_slack checkbox
notify_in_slack_company_ids = Company.objects.filter(notify_in_slack=True).value_list('id', flat=True)
send_slack_notification(notify_in_slack_company_ids)
```
## Rules for fields in Django models
1. The order of fields is defined in
[flake8-class-attributes-order](https://github.com/best-doctor/flake8-class-attributes-order).
1. `DateTimeField` names should end with `_at`.
1. `DateField` names should end with `_date`.
1. For `models.CharField` we use `max_length=255`.
1. If a field has `choices` attribute, then it should be implemented using `django.db.models.TextChoices` or `django.db.models.IntegerChoices`.
1. Define `__str__` method for each model. Make sure that it does not generate a lot of database queries.
1. Every `ForeignKey` field must have a defined `related_name`.
1. Use `.pk` instead of `.id` to get the model identificator.
## Rules for Django urls
1. Don't use decorators inside an app's `urls.py`. Use `method_decorator` in
Class-Based Views for methods defined for their respective HTTP verbs.
More info about this decorator can be found in
[oficial Django docs](https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class).
## Queue naming convention
The main idea is to have queue name prefixed with consuming service name.
Suppose we have three services: `tinker`, `tailor`, `soldier` [, `spy`].
A queue, which is populated and consumed by `tinker` itself, would be called `tinker-default`.
Having need for multiple queues,
one might distinct them with qualifiers like `{consuming service name}-{qual1}-{qualN}`, ex:
`tinker-heavy`, `tinker-light-long`, etc.
As for service interaction, `{consuming service name}-rpc` looks good.
For example, a queue, which is used by `tinker` to send data to `soldier`,
would be named `soldier-rpc`.
Note, that the name has to be the same on the both parties (`tinker`, `soldier`).
================================================
FILE: guides/frontend-styleguide-luchi.md
================================================
# Frontend Styleguide LUCHI
> Этот документ - primary source of truth для frontend-стандартов ЛУЧИ.
>
> Связанный skill: [frontend-styleguide-luchi](https://bestdoctor.gitlab.yandexcloud.net/bestdoctor/common/skills/-/blob/main/frontend-styleguide-luchi/SKILL.md?ref_type=heads)
>
> Если skill неудобен, неполон или устарел, меняем этот гайд или процесс генерации skill, но не редактируем производный skill вручную.
## Базовые принципы
- Единообразие важнее локальных предпочтений.
- Понятность важнее краткости, cleverness, микрооптимизаций и комментариев.
- Максимум проверок выносим в инструменты: TypeScript, ESLint, Prettier, tests.
- Имена отражают бизнес-смысл, а не визуальную реализацию.
- Пользователь всегда получает feedback на `loading`, `empty`, `error`, `success`.
## Tooling
### Linting
- [`eslint-config-bestdoctor`](https://github.com/best-doctor/eslint-config-bestdoctor) считаем legacy-базой.
- Для новых проектов нужен новый, более простой и достаточный конфиг.
- `TODO`: разработать, согласовать и внедрить новый базовый ESLint-конфиг для новых frontend-проектов.
- Проектные правила и плагины допустимы, если они не ломают единый стиль.
- Легаси-нарушения временно переводим в `warn`; по мере рефакторинга исключения убираем.
- Отключение правила линтинга:
- только точечно;
- только с объяснением причины;
- по возможности со ссылкой на задачу.
### Formatting
```json
{
"printWidth": 120,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
```
- Конфиг задается в корневом `.prettierrc`.
- Если проектный конфиг отсутствует, создаем его в этом виде.
- Используем `eslint-config-prettier`, чтобы не конфликтовать с ESLint-конфигом.
- Локальные отклонения от этого конфига допустимы только по явной проектной причине.
### Styling
- Следуем существующему проектному стеку.
- По умолчанию предпочитаем `Tailwind CSS` или `CSS Modules`.
- Не добавляем новый styling-подход без явной причины.
## Архитектура
Используем FSD-lite.
### Слои
- `src/app` - providers, app-level config, global styles.
- `src/pages` - route-level composition.
- `src/entities` - бизнес-сущности; основной слой предметной логики и UI вокруг сущностей.
- `src/shared` - переиспользуемые UI-компоненты, общие utils, constants, types и инфраструктурные модули.
- `src/widgets` и `src/features` - опциональны; добавляются только если проект реально выигрывает от отдельного orchestration-слоя.
### Структура slice
Внутри slice используем директории по назначению:
- `ui` - view-слой.
- `hooks` - hooks и slice-level logic.
- `stores` - локальные и slice-level stores.
- `utils` - локальные утилиты.
- `constants` - константы slice.
- `types` - типы slice.
`pages` внутри `entities` не создаем. Глобальные singleton-like вещи размещаем в `app` или `shared`, а не в отдельном top-level `services`.
### Границы импортов
- Верхний слой может импортировать нижний.
- Импорты между slice одного слоя запрещены.
- Циклические зависимости запрещены.
- `index.ts`-реэкспорты по умолчанию запрещены.
- Barrel-файлы допустимы только как осознанный public API, если это не ухудшает tree-shaking, bundle size и поиск мертвого кода.
## Данные и стейт
### Backend и запросы
Используем `@tanstack/react-query`.
Перед тем как писать ручной клиент или query factory:
1. Проверяем, нет ли уже автогенеренных клиентов, hooks или готового инфраструктурного слоя для работы с backend.
2. Проверяем project-level инструкции (`AGENTS.md`, `.cursorrules`, `README`).
3. Только если готового решения нет, пишем ручную реализацию.
### Query keys
Для ручной реализации используем key factory:
```ts
export function getRequestsKeys<TListParams, TItemKey = string>(
entityName: string,
) {
return {
all: [entityName] as const,
lists: [entityName, "list"] as const,
list: (params: TListParams) => [entityName, "list", params] as const,
items: [entityName, "item"] as const,
item: (key: TItemKey) => [entityName, "item", key] as const,
};
}
```
- `all` - все запросы сущности.
- `lists` - все списки.
- `list(params)` - конкретный список.
- `items` - все item-запросы.
- `item(key)` - конкретная запись.
### Query hooks
- Используем object API: `useQuery({ ... })`, `useInfiniteQuery({ ... })`, `useMutation({ ... })`.
- Хуки должны принимать `options`, если это не делает API хуже.
- `staleTime` и invalidation определяем по характеру данных, а не по привычке.
- На `mutation` по умолчанию инвалидируем релевантные `lists` и `item(id)`.
### Нейминг query/mutation hooks
- Одна запись: `useUserQuery`
- Специфический запрос: `useUserByEmailQuery`
- Список: `useUsersListQuery`
- Бесконечный список: `useInfiniteUsersListQuery`
- Бесконечный поиск: `useInfiniteSearchUsersQuery`
- Создание: `useCreateUserMutation`
- Обновление записи: `useUpdateUserMutation`
- Обновление поля: `useUpdateUserNameMutation`
- Удаление: `useRemoveUserMutation`
### Формы и локальный стейт
- Для форм используем `react-hook-form`.
- Все, что может быть локальным, оставляем локальным.
- Глобальный стейт - исключение, а не норма.
- Глобальный store используем только для реально shared-состояния: авторизация, глобальный виджет, cross-page state.
### Loading / Empty / Error
- Предпочитаем `Suspense + ErrorBoundary + Skeletons`.
- Не оставляем пользователя на пустом экране.
- Если данных нет - показываем empty state.
- Если запрос упал - показываем понятный error state и следующий шаг.
## Нейминг
### Общие правила
- Используем английский язык.
- Название должно быть семантичным и бизнес-ориентированным.
- Понятность важнее длины и "красоты".
- Называем сущность по роли, а не по иконке, цвету или layout.
Хорошо: `TogglePasswordVisibilityButton`
Плохо: `EyeIconButton`
### Функции
- Имя функции начинается с глагола.
- `get*` - возвращает значение.
- `validate*`, `check*` - возвращает `boolean`.
- `format*` - преобразует данные.
- `set*` - пишет в state/store/ref.
- `handle*` - внутренний handler.
- `on*` - callback prop компонента.
### Переменные
- Переменная - существительное.
- Boolean-флаги: `is*`, `has*`, `can*`, `should*`, `was*`, `did*`.
- Коллекции называем во множественном числе: `users`, `errors`, `phoneNumbers`.
- Количество сущностей - `*Number`.
- Порядковый номер - `*Index`.
- Raw/form payload - `*Data`.
- Состояние называем как состояние: `isOpen`, `isEnabled`; не смешиваем state и action.
### Компоненты, файлы, директории
- Компоненты - `PascalCase`.
- Файлы и директории - `camelCase`.
- Имя файла по возможности совпадает с главным export.
### Naming reference
- Хорошее имя описывает роль в бизнес-логике, а не реализацию.
- `usersPhoneNumbers` - номера телефонов пользователей.
- `userPhoneNumbers` - номера телефонов одного пользователя.
- `usersPhoneNumber` - плохое имя: число сущностей и число коллекции конфликтуют.
- Для UI-компонентов имя строится от назначения: `SubmitOrderButton`, `UserProfileCard`, `TogglePasswordVisibilityButton`.
- Имена вроде `BlueCard`, `LeftPanel`, `EyeIconButton` допустимы только если цвет, позиция или иконка и есть бизнес-смысл сущности, что почти никогда не так.
## Дизайн кода
### Single responsibility
Одна функция делает одну задачу и делает ее хорошо.
Признаки, что функцию пора делить:
- в названии есть `And`;
- `boolean`-аргумент переключает поведение;
- функция разрастается на сотни строк;
- внутри смешаны orchestration, data transform и view logic.
### Параметры функций
- `1-2` аргумента - обычно positional.
- `3+` аргумента - объект.
- Один объект тоже допустим, если функция будет расширяться.
- Исключение: thin wrapper над backend/API может принимать много полей, если функция почти не содержит логики.
### Комментарии
- Комментарии по умолчанию не пишем.
- Предпочитаем self-documenting code через нейминг и декомпозицию.
- Допустимы:
- причина отключения линтинга;
- `TODO` / tech debt с ссылкой на задачу;
- краткое объяснение нетривиального решения, которое нельзя сделать очевиднее кодом.
## Reference
### FSD-lite example
```text
src/
app/ # providers, app config, global styles
pages/ # route-level composition
entities/ # User/, Order/, Product/
shared/ # ui/, utils/, constants/, types/
widgets/ # optional
features/ # optional
```
### Forbidden imports
- `entities/user` -> `entities/order` запрещено.
- `shared/ui/button` -> `entities/user` запрещено.
- `pages/userPage` -> `pages/orderPage` обычно запрещено; общую логику выносим ниже.
- Если orchestration нужен между несколькими entities, поднимаем его в `pages`, `widgets` или `features`.
### React Query reference
- Предпочитаем generic-обертки с `options`, чтобы хук можно было переиспользовать без копипаста.
- Для списков используем `list(params)`, для одной сущности `item(id)`.
- `invalidateQueries` бьет либо по конкретному `item`, либо по набору `lists`; не инвалидируем `all`, если можно сузить область.
- Если проект использует автогенерацию API-клиента и hooks, придерживаемся project-native pattern, а не насаждаем manual factory поверх него.
## Canonical examples
### Query key factory
```ts
export function getRequestsKeys<TListParams, TItemKey = string>(
entityName: string,
) {
return {
all: [entityName] as const,
lists: [entityName, "list"] as const,
list: (params: TListParams) => [entityName, "list", params] as const,
items: [entityName, "item"] as const,
item: (key: TItemKey) => [entityName, "item", key] as const,
};
}
```
### Query hook
```ts
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
type FetchError = { message: string };
type UserResponse = { id: string; name: string; email: string };
type UserResult = UserResponse;
const userKeys = getRequestsKeys<never, string>("users");
async function getUser(id: string): Promise<UserResponse> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
}
type UseUserQueryOptions<TResult> = Partial<
UseQueryOptions<UserResponse, FetchError, TResult>
>;
export function useUserQuery<TResult = UserResult>(
id: string,
options?: UseUserQueryOptions<TResult>,
) {
return useQuery<UserResponse, FetchError, TResult>({
queryKey: userKeys.item(id),
queryFn: () => getUser(id),
staleTime: 60_000,
...options,
});
}
```
### Mutation hook
```ts
import {
useMutation,
useQueryClient,
type UseMutationOptions,
} from "@tanstack/react-query";
type FetchError = { message: string };
type UserResponse = { id: string; name: string };
type UpdateUserParams = { id: string; name: string };
async function updateUser(params: UpdateUserParams): Promise<UserResponse> {
const response = await fetch(`/api/users/${params.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
}
export function useUpdateUserMutation(
options?: UseMutationOptions<UserResponse, FetchError, UpdateUserParams>,
) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateUser,
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: userKeys.lists });
queryClient.invalidateQueries({ queryKey: userKeys.item(data.id) });
},
...options,
});
}
```
### Suspense boundary
```tsx
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
export function UserPage({ userId }: { userId: string }) {
return (
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId={userId} />
</Suspense>
</ErrorBoundary>
);
}
```
## Требования к derived skill
- Derived skill создается из этого guide, а не дописывается вручную.
- Guide должен содержать не только policy, но и critical reference/examples, которые нужны для корректной генерации кода агентом.
- Progressive guide должен содержать не только policy, но и нормативную базу для производных материалов.
- Derived skill по умолчанию строится как progressive disclosure:
- `SKILL.md` содержит только quick start, critical defaults и ссылки;
- подробности выносятся в отдельные derived files вроде `reference.md`, `examples.md`, `config.md`;
- ссылки остаются one-level deep.
- Skill берет из guide:
- core rules;
- architecture constraints;
- naming rules;
- React Query / state rules;
- canonical examples, если они нужны агенту для качественной генерации.
## Короткий checklist
- Архитектура укладывается в FSD-lite.
- Импорты соблюдают границы слоев.
- Query/mutation hooks названы консистентно.
- Loading, empty и error states явные.
- Названия отражают бизнес-смысл.
- Линт и форматирование не подавлены без причины.
================================================
FILE: guides/guide_skill_workflow.md
================================================
# Guide-Skill Workflow
Этот документ описывает, как вести связку `гайд -> производный skill`.
## Статус модели
- Репозиторий гайдов хранит primary source of truth.
- Репозиторий skills хранит все skills в ИТ.
- Если skill основан на гайде, гайд первичен, skill вторичен.
- Skills без привязки к гайдам существуют самостоятельно и считаются first-class citizen.
## Непереговорные правила
1. Меняем сначала гайд, потом пересоздаем skill.
2. В guide-derived skill обязательно есть ссылка на первичный гайд.
3. В гайде обязательно есть ссылка на связанный skill.
4. Если skill получился неудобным, меняем гайд или процесс генерации, но не правим skill вручную.
5. Название, терминология и scope guide и derived skill должны совпадать.
6. Нормативные `reference`, `examples`, `config` и аналогичные материалы должны быть встроены в primary guide, а не жить как единственный источник правил вне него.
7. Guide-derived skill по умолчанию делаем progressive: в `SKILL.md` только essential operational instructions, а детали выносим в отдельные linked files одного уровня вложенности.
## Workflow для человека
1. Обновить первичный гайд.
2. Проверить, что текст стал короче, точнее и не потерял обязательные правила, reference-блоки, canonical examples и config snippets.
3. Если важная часть стандарта живет во внешнем reference/examples/config-файле, перенести ее нормативную часть в guide.
4. Пересоздать связанный skill из гайда в progressive disclosure-формате:
- `SKILL.md` для essential instructions;
- `reference.md` / `examples.md` / `config.md` или аналогичные файлы для деталей;
- ссылки только на файлы одним уровнем ниже.
5. Проверить двусторонние ссылки `guide <-> skill`.
6. Проверить, что resource-файлы не содержат новых правил, которых нет в guide.
7. Обновить индексы и README, если поменялись имя или путь.
8. Убедиться, что старый skill-name или старый guide-path не остались в репозитории.
## Workflow для агента
1. Найти первичный гайд и считать его источником правды.
2. Если задача касается guide-derived skill, сначала править гайд.
3. После обновления гайда пересоздать skill как краткую operational-версию:
- меньше воды;
- только agent-relevant instructions;
- обязательная ссылка на guide;
- без ручных локальных правок поверх гайда;
- с progressive disclosure через linked resource files.
4. В `SKILL.md` держать только короткий quick start, critical defaults и ссылки на ресурсы.
5. Вынести детали в отдельные файлы вроде `reference.md`, `examples.md`, `config.md`, если они действительно помогают и остаются derived from guide.
6. Если важные примеры, reference-блоки или config snippets находятся вне guide, сначала встроить их в guide и только потом регенерировать skill.
7. Если derived skill выглядит слабым, исправлять guide или инструкцию генерации.
8. Не переносить ответственность за стандарты из guide в skill.
## Checklist синхронизации
- Имя guide и skill согласованы.
- Guide содержит актуальную ссылку на skill.
- Skill содержит актуальную ссылку на guide.
- В skill нет правил, которых нет в guide.
- В guide нет ключевых правил, потерянных при генерации skill.
- В guide есть все нормативные fragments из внешних reference/examples/config-материалов.
- `SKILL.md` остается компактным и не дублирует весь guide.
- Детали вынесены в linked resource files одного уровня вложенности.
- README и служебные индексы обновлены.
================================================
FILE: guides/infra_infosec_guide.md
================================================
# Гайд по безопасности
## Что это такое
Краткое описание технологий и инструментов, которые мы применяем в работе для обеспечения безопасности Linux хостов и
сохранности данных пользователей. При выборе инструментов обеспечения безопасности, мы руководствуемся здравым смыслом
и лучшими практиками доступными сообществу.
## Что мы делаем
Мы используем подходы Configuration as code и Infrastructure as code для того чтобы понимать что изменяется в нашей
инфраструктуре, кем и с какой целью. Для этого мы используем инструменты автоматизации: `ansible`, `gitlab`, `terraform`.
Мы хотим тестировать применённую конфигурацию на соответствие разумным требованиям безопасности данных и хостов,
предоставляемыми нашим бизнесом, согласовывая их с общепринятыми базовыми требованиями от сообщества.
Поэтому мы используем инструмент для тестирования конфигурации [Chef Inspec](https://inspec.io) и следим за развитием
проекта [DevSec Hardening Framework](https://dev-sec.io/)
Мы хотим следить за базами уязвимостей/уязвимых пакетов в используемых дистрибутивах и своевременно обновлять ПО.
Поэтому мы автоматизировали этот процесс и настроили еженедельную отправку отчётов о состоянии установленных пакетов с
помощью [Vuls](https://vuls.io/). После получения отчёта мы применяем патчи/устанавливаем критические обновления по расписанию.
================================================
FILE: guides/opensource_guide.md
================================================
# Правила создания и поддержки opensource-проектов в BestDoctor
## Мотивация
Сейчас на <https://github.com/best-doctor/> 20+ проектов в разной
стадии разработки. Мы хотим поддерживать их в нормальном состоянии, чтобы
приносить больше пользы сообществу и самим себе.
В этом гайде собраны правила, которых мы стараемся придерживаться при разработке
и сопровождении наших opensource-проектов и процесс этого сопровождения.
## Процесс сопровождения
Основной элемент сопровождения – еженедельный чек проектов по этому гайду.
На это выделяется 1-2 часа в неделю, чтобы найти новые поломки, ответить на
вопросы в issues, сделать ревью пул-реквестов, обновить зависимости,
зачинить билды. Этот процесс нужен не для развития проектов, а для их
поддержки. Основных целей у этого процесса две: минимизировать количество
критичных нарушений этого гайда и максимизировать количество проектов,
которые полностью ему соответствуют.
Проверять проекты на соответствие гайду руками – муторное и неблагодарное
занятие, поэтому в наших кузницах уже разрабатывается `opensource_watchman`
– скрипт, который делает всё сам и показывает список нарушений по каждому
проекту. Скоро он будет готов и ссылка на него будет тут.
Развитие проектов – то, к чему может присоединиться кто угодно, в том числе мы.
Для этого у нас есть открытые задачи, понятные гайды, CI и правила участия
в проектах.
## Язык проекта
Основной язык всех проектов – английский. Он используется везде: в текстах
коммитов, комментариях, задачах, при общении в пул-реквестах и в документации.
Это нужно, чтобы наши проекты могли принести пользу максимальному
количеству людей.
Исключений может быть два. Первое – репозитории с текстовой информацией, а не
кодом (например, этот репо). Такие проекты могут быть на русском. Второе –
проекты, актуальные только для русского языка. Пример такого проекта
(извне BestDoctor, правда) – это
<https://github.com/Melevir/rozental_as_a_service>. Он работает только с русским
языком и его основной язык – русский.
Если проект на русском, стоит подумать о его переезде на английский полностью
или поддержки нескольких языков с процессом актуализации переводов.
## Ридми и документация
У каждого проекта должен быть ридми с исчерпывающей информацией: что это,
зачем оно нужно, как работает, как установить, как запустить,
что для этого нужно, пример использования, как сделать вклад в проект.
Для унификации основной ридми должен лежать в корне проекта и
называться `README.md`.
Ридми и другая документация должна валидироваться с помощью `mdl`.
В ридми должны быть бейджики со статусом билда и процентом покрытия
кода тестами. Остальные бейджики можно не добавлять.
## Поддерживаемые версии языков
Мы стараемся использовать в opensource-проектах те же технологии, которые
используем в бою.
Вне зависимости от выбранного языка, наши проекты должны поддерживать
минимум две последние минорные версии языка.
## CI и сторонние сервисы
Для унификации мы используем Travis CI для, собственно, CI и Codeclimate
для дополнительного статического анализа и подсчёта покрытия тестами.
В Тревисе должна быть настроена основная таска, которая запускается на пуш
и прогоняет все проверки (стиль кода, типизация, тесты, mdl,
safety и все-все-все) и тесты. Если билд зелёный – значит, все
проверки проходят.
Кроме этого в Тревисе стоит настроить еженедельную крон-джобу, которая делает
всё то же, но без коммита. Это позволяет вовремя отловить обратно несовместимые
обновления незамороженных зависимостей, найденные новые CVE и прочие ништяки,
о которых хорошо бы знать как можно раньше.
## Безопасность и зависимости
Мы заботимся о том, чтобы наш код не наносил вреда и чтобы с его помощью
нельзя было нанести вред. Для этого мы действуем по двум направлениям:
следим за нашим кодом и за нашими зависимостями.
В нашем коде мы стараемся не допускать распространённых ошибок, связанных с
безопасностью. Для хоть какой-то автоматической проверки этого в коде на
Python мы используем dlint.
Для мониторинга обновлений безопасности в наших зависимостях на Python
мы используем safety. Он должен быть в билде на пуш и, что самое важное,
еженедельном билде. Если найдётся уязвимость в одной из зависимостей,
сломается еженедельный билд и мы об этом узнаем.
## Версионирование
Во всех проектах используем
[семантическое версионирование](https://semver.org/lang/ru/).
## Политика релизов
Код может лежать на Гитхабе, но не быть зарелижен, не больше 2 недель.
Важно понимать, что 2 недели – это допустимый, но не желательный срок.
В идеале код отправляется в релиз сразу после пуша. У нас тут ZDD или как?
## Правило последнего коммита
Последний коммит в любой проект должен быть меньше, чем 6 месяцев назад.
За это время точно что-то произошло даже если проекта не дорабатывается:
приехала новая версия языка, обновились зависимости или наши требования
к нашим проектам.
Если мы не планируем дорабатывать проект, это нужно сделать явным:
написать дисклеймер в ридми и заархивировать проекта на Гитхабе.
## Покрытие кода тестами
Минимальное покрытие кода тестами должно составлять 80%.
Это важно не только для устойчивой дальнейшей разработки проекта, но и для
уверенности в проекте тех, кто только решает, использовать ли его.
Тесты должны гоняться в билде, иначе от них почти нет смысла.
Для достижения этой цели мы используем и юнит-тесты и интеграционные.
## Работа с задачами
У каждого проекта должно быть 3+ открытых актуальных задачи.
Это нужно, чтобы помочь сделать вклад в проект тем, кто этого хочет.
На свежесозданные задачи мы отвечаем в течение 14 дней.
Нет ничего хуже проекта, мейнтейнер которого не обрабатывает задачи.
## Работа с пул-реквестами
Все входящие пул-реквесты обрабатываем за 7 дней: делаем ревью и просим
доработать или мерджим. Желательно быстрее: если кто-то сделал пул-реквест,
нам важно сделать его опыт взаимодействия с мейнтейнером как можно более
быстрым.
================================================
FILE: guides/performance_review_guide.md
================================================
# Правила проведения перфоманс-ревью в BestDoctor
Перфоманс ревью – встреча, которая проводится с каждым сотрудником it-отдела BestDoctor дважды в год.
На ней сотрудник и компания синхронизирует взаимные ожидания и, возможно, сотруднику повышают оклад.
Кроме основных перфомансов есть ежемесячные мини-перфомансы. Они не привязаны к деньгам, но помогают
отследить прогресс в выполнении задач, о которых договорились на "большом" перфомансе
и сделать процесс проведения перфоманс-ревью более прозрачным и предсказуемым.
Повышение оклада обычно происходит за уже достигнутые успехи, которые находятся за пределами
основных рабочих обязанностей сотрудника. Мы не повышаем оклад разработчику за то, что он выполняет
задачи в срок потому, что считаем это частью обязательного плана, за который сотрудник получает
зарплату. Мы повышаем оклад разработчику за то, что он взял на себя управление командой,
предложил и довёл до боя несколько инженерных подпроектов, много выступал на митапах,
сделал вклад в развитие опенсорса в компании и другие подобные вещи. Все они должны быть
полезны для компании и не являться обязательными для сотрудника.
Немного подробнее про каждую из встреч.
## Перфоманс-ревью
**Периодичность**: Проводится каждые полгода с момента устройства сотрудника на работу.
**Проводит**: CTO.
**Цели**:
- Найти слабые места.
- Отметить успехи за прошедшие полгода.
- Составить план на следующие полгода.
**Пререквизиты**:
- Обратная связь от коллег.
- План с предыдущего ревью (опционально).
**План беседы**:
- Узнать ожидание сотрудника от встречи.
- Выдача негатива:
- Выдать объективную обратную связь по слабым местам.
- Обсудить полученный негатив, насколько он соответствует действительности и внутренним ощущениям человека.
- Обсудить готовность работать над исправлением слабых мест.
- Выдача позитива:
- Отметить успехи за прошедшие полгода.
- Сообщить соотношение негативного и позитивного и общем впечатлении.
Это нужно для более адекватной картины, т.к. негативные и позитивные части могут занимать время,
не пропорциональное количеству этого самого негатива и позитива
- Сбор обратной связи по обратной связи:
- Спросить, насколько ожидания от встречи совпали с реальностью.
- Узнать про слабые места и успехи с точки зрения сотрудника.
- Составление плана развития:
- Спросить про то, в какой области хочется развиваться.
- Уточнить, какое видение компании на рост сотрудника и как это бьётся с его желаниями.
- Проверить как план развития бьётся с грейдами.
В результате ревью оба её участника синхронизированы в ожиданиях и имеют зафиксированный план развития
сотрудника до следующего перфоманса. Успешное выполнение плана является необходимым требованием для повышения.
## Промежуточное перфоманс-ревью
**Периодичность**: Проводится каждый месяц вместо One-to-One встречи до 6 числа.
**Проводит**: Тимлид для линейных членов команды / CTO для тимлидов и юнитов.
**Цели**:
- Обеспечить более плавный прогресс по плану с перфоманса.
- Синхронизироваться в ожиданиях от грядущего перфоманса.
**План беседы**:
- Посмотреть в план развития.
- Оценить прогресс за прошедший месяц:
- Спросить сотрудника про его оценку прогресса.
- Дать обратную связь по прогрессу.
- Составить план на следующий месяц.
Если встречу проводит тимлид, то он перед встречей валидирует свой фидбек для сотрудника у CTO, чтобы
результаты промежуточных перфомансов не расходились с результатами "больших" перфомансов.
================================================
FILE: guides/python_styleguide.md
================================================
# Правила написания кода на Python в BestDoctor
## Мотивация
Мы разрабатываем здоровенный продукт с кучей функциональности.
Более того, нам потом этот код ещё поддерживать и развивать.
Для этого код должен быть гибким, слабо связанным, соблюдать принципы
DRY, KISS и ещё много чего должен. Иначе в долгосрочной перспективе
вся лажа вылезет наружу и будет больно разработчикам, бизнесу и поддержке.
Такой лажи надо избегать.
Чтобы получать код, подходящий нашим требованиям, надо постоянно думать.
Постоянно думать сложно: выходит медленно и постоянно ошибаешься.
Поэтому некоторые кусочки думанья, которые касаются кода, мы выносим в
стайлгайд. Теперь в некоторых случаях можно не думать, а просто пользоваться правилом.
К гайду нужно относиться не как к строгому своду правил, а как к опытному советнику.
Соблюдаем правила не ради правил, а чтобы получать классный код, отвечающий нашим требованиям.
Нам также важно, чтобы код нравился команде разработки. Поэтому наш гайд
заточен под участников нашей команды и их вкусы. Вам это может быть не по вкусу.
Это нормально. Расслабьтесь. Выдохните. Закажите виски сауэр.
## Можно нарушать
Любое правило из этого гайда можно нарушить. Более того, в некоторых
ситуациях не нарушать гайд – это ошибка. Такие ситуации случаются редко,
и в каждой из них нужно иметь железную аргументацию для нарушения.
Ни одно из правил не соблюдаем ради соблюдения правила.
Бывают ситуации, когда можно сделать `import *`, `except Exception`,
отступы табами и длиннющие строки. Смело это делаем, если уверены, что это улучшит код.
Устраивать пляски с бубном ради Карго-культа не для нас.
## Последний Python
В бою 3.9, на стейджинге 3.9, на всех инстансах 3.9,
думаем тоже на 3.9.
## PEP8
Любим и соблюдаем PEP8, за исключением длины строки. У нас жёсткая граница - 100 символов.
Используем [black](https://github.com/psf/black). Кавычки - одинарные.
## Комментарии
### Нет необходимых комментариев
Есть правила, которые требуют написание комментариев в некоторых местах.
Например, у публичных модулей, классов, методов.
У нас таких правил нет. Комментарии необходимо писать там, где они к месту:
когда функция делает что-то неочевидное, обрабатывается хитрый случай.
Если из названия сущности непонятно, что она делает, сначала стоит
попробовать её переименовать, чтобы было понятно.
### Однострочные докстринги
Короткий способ написать докстринг, который помещается на одной строке.
Переносить не надо, заканчивать точкой обязательно.
Плохо:
```python
"""
Подсчёт статистики по юрлицам клиентов.
"""
```
Плохо:
```python
"""Подсчёт статистики по юрлицам клиентов"""
```
Хорошо:
```python
"""Подсчёт статистики по юрлицам клиентов."""
```
### Многострочные докстринги
Нужны, когда есть что сказать и одной строки мало.
Структура такая: первая строка описывает что это такое и заканчивается точкой.
Отбивается от тела комментария пустой строкой.
У комментария нет дополнительных отступов от кавычек.
Плохо:
```python
"""
Активный период обслуживания.
Работает как надо, если у человека не более одного активного слота
"""
```
Плохо (точка потерялась):
```python
"""
Активный период обслуживания
Работает как надо, если у человека не более одного активного слота
"""
```
Хорошо:
```python
"""
Активный период обслуживания.
Работает как надо, если у человека не более одного активного слота.
"""
```
### Многострочный текст
Выравнивание кавычек/скобок как для функций при многострочном вызове;
текст пишется со следующей строки c отступом в 4 пробела:
Если количество пробелов некритично:
```python
query = """
mutation {{
detachSlot({0}) {{
slot {{
id
endDate
}}
}}
}}
"""
```
Иначе:
```python
author = 'Георг наш, понимаете ли, Гегель.'
fragment = (
'Абсолютная идея есть для себя, потому что в ней нет ни перехода, '
'ни предпосылок и вообще никакой определенности, которые не были бы '
'текучи и прозрачны; она есть чистая форма понятия, которая созерцает '
'свое содержание как самое себя. Она есть свое собственное содержание, '
'поскольку она есть идеальное различение самой себя от себя, и одно из '
'этих различий есть тождество с собой, которое, однако, содержит в себе '
'тотальность форм как систему содержательных определений. '
'Это содержание есть система логического.'
f'({author})'
)
```
Если мультилайн нужен при инстанцировании объекта/вызове функции:
```python
raise GraphQLError(
'Эх, '
'Вася-Вася!' # noqa: C812
)
```
Или можно еще так:
```python
raise GraphQLError((
'Эх, '
'Вася-Вася!'
))
```
### Инлайновые комментарии
Можно и нужно писать, чтобы описать неявный случай, сослаться на
тикет или вики.
Не пишем, если хотим пошутить, дать коду характеристику или разбить
код на логические блоки. Для первых двух пунктов есть кухня,
для третьего – рефакторинг кода на более мелкие куски.
Плохо:
```python
# это какое-то говно
```
Плохо:
```python
# получаем данные пользователя
...
# формируем контекст для отчёта
...
# генерируем отчёт
...
```
Хорошо:
```python
# подробнее о формулах можно почитать в BES-482
```
### Комменты TODO и FIXME
Пишем комменты TODO и FIXME, когда вспоминаем что-то важное
в процессе работы над фичей и не хотим отвлекаться,
но перед коммитом всё вычищаем: фиксим или создаём тикеты.
Это нужно по двум причинам: во-первых, эти комментарии часто висят
годами и с ними ничего не происходит, а во-вторых, они создают
впечатление поломанности проекта.
Задачам по доработке и рефакторингу место в трекере задач, а не в коде.
### Ссылки в инлайновых комментариях
Инлайновый коммент не должен состоять из одной только ссылки.
Лучше добавлять рядом пояснение. Это позволяет понять суть без того, чтобы
ходить по ссылке.
Плохо:
```python
# https://stackoverflow.com/q/184618/3694363
```
Хорошо:
```python
# empties the cache properly: https://stackoverflow.com/q/184618/3694363
```
## Логирование
### Язык
Все логи пишем на английском языке. Это связано с тем, что логи пишутся не
только нашим кодом, но и сторонними библиотеками, которые мы используем - как
правило, это логи на английском языке. При анализе логов удобно, когда все на
одном языке - это прибавляет консистентности.
Плохо:
```python
logger.info('Запрашиваю баланс')
```
Хорошо:
```python
logger.info('Requesting balance')
```
### Structlog
Для логирования мы используем [structlog](https://www.structlog.org/en/stable/).
Он умеет создавать логи в JSON, быстро работает и имеет гибкие настройки.
Для проектов на Django - [django-structlog](https://django-structlog.readthedocs.io/en/latest/).
### Использование контекста
Для доступа к переменным контекста вместо threadlocal контекста мы используем
встроенный [contextvars](https://www.structlog.org/en/stable/contextvars.html).
Вот несколько причин, почему contextvars лучше, чем threadlocal:
- Потокобезопасность: contextvars гарантирует потокобезопасность,
тогда как threadlocal не обеспечивает этого.
- Читаемость кода: контекстные переменные contextvars легки для чтения и
понимания, в отличие от threadlocal, который может вызвать путаницу в коде.
- Поддержка возможностей асинхронности: contextvars позволяет корректно
работать с асинхронными вызовами, в то время как threadlocal может отстрелить
ноги
В целом, использование contextvars вместо threadlocal может повысить
безопасность, упростить код и обеспечить правильную работу с асинхронными
операциями.
## Строковые литералы и форматирование
Используем одинарные кавычки для строковых литералов, но избегаем
обратных слешей в строках.
Для форматирования используем `.format` или эфстринги с нумерованными или
именованными аргументами. Не складываем строки с помощью `+`.
Эфстринги используем по желанию и не напихиваем внутри много логики.
Плохо:
```python
'%s %s' % (first_name, last_name)
```
Плохо:
```python
'{} {}'.format(first_name, last_name)
```
Хорошо:
```python
'{0} {1}'.format(first_name, last_name)
```
## Импорты
Используем [isort](https://github.com/PyCQA/isort).
Сущности извне стандартной библиотеки импортируем точечно:
перечисляем нужные функции и классы.
Не импортируем весь модуль и точно не делаем `import *`.
Если происходит коллизия имён, используем `as`.
Модули из стандартной библиотеки импортируем целиком.
Импорты делим на три блока, по PEP8.
Сортировка внутри блока не регламентирована.
Плохо:
```python
from collections import Counter
```
Плохо:
```python
from core import models
```
Хорошо:
```python
import collections
from core.models import Patient
```
## Код в `__init__.py`
В `__init__.py` можно писать только импорты. Больше ничего нельзя.
## Форматирование вызовов
Если вызов вместе со всеми аргументами умещается на одну строку,
никаких переносов не ставим.
Если вызов не умещается на одну строку, но при переносе
после открывающей скобки всё умещается на две, делаем так.
Если аргументов много, есть вложенные вызовы или вызов не помещается
на две строки, используем расширенное форматирование.
При таком форматировании можно ставить несколько аргументов на одну
строку, если они схожи по смыслу. Например, `date_from` и `date_to`
или `to` и `on_delete`.
Плохо:
```python
recieved_date = models.DateField(
'Дата получения', auto_now_add=True,
) # если аргументы помещаются на одной строке, то скобку стоит оставлять там же
legal = models.ForeignKey(
'bestdoctor.ClinicLegalEntity',
models.CASCADE,
'registries', 'registry',
verbose_name='юридическое лицо клиники',
) # скобка с лишним отступом
```
Хорошо:
```python
recieved_date = models.DateField(
'Дата получения', auto_now_add=True)
legal = models.ForeignKey(
'bestdoctor.ClinicLegalEntity',
models.CASCADE,
'registries', 'registry',
verbose_name='юридическое лицо клиники',
)
```
Хорошо:
```python
legal = models.ForeignKey(
'bestdoctor.ClinicLegalEntity',
models.CASCADE,
'registries',
'registry',
verbose_name='юридическое лицо клиники',
)
```
## Вызов функций и аргументы
При вызове функций указываем аргументы, если они не очевидны из названия функции.
Если очевидно, всё равно можно писать названия аргументов.
Плохо:
```python
serializer.is_valid(True)
```
Хорошо:
```python
serializer.is_valid(raise_exception=True)
```
## Запятые
У всех многострочных перечислений ставим запятую после последнего элемента.
Это касается не только списков, но и вызовов, туплов и всего
остального многострочного с запятыми.
Если перечисление однострочное, то запятой после последнего элемента не ставим.
Исключение – `tuple` с одним элементом.
Плохо (потерялась последняя запятая):
```python
list_filter = (
'legal', 'is_finished', 'is_paid', 'client_registry__record__report_date'
)
```
Плохо:
```python
Act.objects.create(
date=today(),
act_type='tech',
report_date=report_date,
is_finished=False
)
l = [1,2,3,] # лишняя запятая в конце однострочного перечисления
```
Хорошо:
```python
list_filter = (
'legal', 'is_finished', 'is_paid', 'client_registry__record__report_date',
)
Act.objects.create(
date=today(),
act_type='tech',
report_date=report_date,
is_finished=False,
)
l = [1,2,3]
t = (1,) # без этой запятой будет не tuple, а int
```
## Неиспользуемый код
Мы хотим, чтобы в нашем коде было как можно меньше багов.
Классный способ избавиться от багов – избавиться от кода.
Нет кода – нет проблем.
Поэтому мы удаляем из кодовой базы весь код, который можем.
Закомментированный код, неактуальные фичи,
какие-то разовые скрипты – всё под нож.
Потом их можно несложно вытащить т.к. все коммиты привязаны к тикету в таск-менеджере,
поэтому если понадобится найти удалённый код, всегда можно отыскать тикет,
потом его коммиты, а потом накатить их.
Всё, что не нужно прямо сейчас и не понадобится в течение месяца – под нож.
## Данные в настройках
Мы стараемся класть данные туда, где им место, и в последнюю очередь храним их в настройках. Вот пример, в котором
мы переносим данные в БД с помощью `BooleanField`:
Было:
```python
# settings.py
NOTIFY_IN_SLACK_COMPANY_IDS = [123, 456, 789]
# code
send_slack_notification(NOTIFY_IN_SLACK_COMPANY_IDS)
```
Стало:
```python
# Теперь это просто компании с галочкой notify_in_slack
notify_in_slack_company_ids = Company.objects.filter(notify_in_slack=True).value_list('id', flat=True)
send_slack_notification(notify_in_slack_company_ids)
```
## Правила для полей Django-моделей
1. Порядок указания полей регламентирован в
[flake8-class-attributes-order](https://github.com/best-doctor/flake8-class-attributes-order).
1. Названия полей типа `DateTimeField` заканчиваем на `_at`.
1. Названия полей типа `DateField` заканчиваем на `_date`.
1. Для `models.CharField` всегда используем `max_length=255`.
1. Если у поля есть `choices`, то оформляем его через `django.db.models.TextChoices` или `django.db.models.IntegerChoices`.
1. У каждой модели сразу делаем `__str__` и проверяем, что оно не генерирует много запросов к БД.
1. Всем полям типа `ForeignKey` обязательно указываем `related_name`.
1. Если нужно получить айди модели, используем `.pk`, а не `.id`.
## Правила для Django urls
1. В `urls.py` приложения не используем декораторы. Для Class-based views используем `method_decorator` для
cоответствующих типов HTTP запросов, подробнее можно почитать о данном декораторе в
[официальной документации Django](https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class).
## Называние очередей
Суть задумки -- начинать название очереди с имени сервиса, который её вычитывает.
Представим себе набор сервисов `tinker`, `tailor`, `soldier` [, `spy`].
Очередь, в которой `tinker` хранит свои, внутрисервисные задачи,
которые сам создаёт и выполняет, должна называться `tinker-default`.
Если очередей надо будет иметь несколько, можно называть их по характеру задач, например:
`tinker-heavy`, `tinker-light-long` и т.д.
Что касается взаимодействия сервисов, предлагаем шаблон `{consuming service name}-rpc`:
например, очередь, в которую `tinker` будет складывать запросы для `soldier`,
будет называться `soldier-rpc`.
Отметим, что название это будет одинаковым на стороне обоих сервисов.
================================================
FILE: guides/python_test_styleguide.md
================================================
# Гайдлайны для тестов
## Для кого
Эти правила подразумевают, что их читает Python-разработчик, а не тестер или фронтендер. Так что отделу QA эти правила
не подойдут.
## Мотивация
Тесты, как и любой код, нужно поддерживать в чистоте. Один из способов улучшить сопровождаемость тестов — ввести список
правил. Правила упростят ознакомление с существующими тестами и подскажут, как писать новые.
Эти гайдлайны являются _вводными_. Добавлять сразу много строгих правил сложно и непрактично, лучше начать с малого.
## Отношение к другим документам
Во время написания тестов соблюдается
[Python Styleguide](https://github.com/best-doctor/guides/blob/master/guides/python_styleguide.md).
## Дисклеймер
Мы пишем тесты с использованием `pytest` и плагинов для него. Рудименты, вроде `unittest.TestCase`, под запретом.
## Два вида тестов
Разработчикам надо тестировать две вещи:
1. Что фича работает
1. Что функции и методы, составляющие фичу, работают как надо
А соответственно нужны два вида тестов:
1. Интеграционные тесты, которые тестируют фичу целиком и проверяют успешность интеграции разных компонентов системы
1. Юнит-тесты - легковесные тесты, проверяющие отдельные методы и функции
## Куда класть тесты
Тесты для `mail/signals.py` должны лежать в `mail/test_integration/tests.py` или `mail/tests/test_integration/test_signals.py`.
Более общее правило: тесты для `<foo>/<bar>.py` находятся в `<foo>/test_<test_type>/tests.py`
или в `<foo>/tests/test_<test_type>/test_<bar>.py`. Если файл `test_<bar>.py` становится слишком большим,
разносим его на отдельный модуль – `<foo>/tests/test_<test_type>/test_<bar>/test_<some_bar_feature>.py`
## Как именовать тесты
Именование тестов должно отображать тестируемую сущность и тестируемый кейс, схема выглядит так: `test__<функция>__<уточнение кейса>`.
Если возникли сложности с уточнением кейса, возможно ваш тест стоит разбить на несколько.
Примеры: `test__create_company__without_company_service_period`, `test__create_company__with_active_company_service_period`
## `selenium` vs `django.test.Client`
Чтобы протестировать фичу полностью, мы используем `django.test.Client`.
Мы не используем `selenium` по двум причинам:
1. Он тяжёлый
1. Не существует сценариев, в которых нам требуется его функциональность
## Скорость выполнения
Мы пока не обращаем внимания на скорость выполнения, но избегаем слишком долгих тестов.
## `success_case` и `error_case`
`success_case`-тест отвечает на вопрос «как должен вести себя код при корректном инпуте». На каждую фичу и её
составляющую должен приходится `success_case`-тест.
Если фича/составляющая должна как-то особо обрабатывать некорректный ввод, под неё пишется `error_case`-тест.
## Параметризация
Чтобы избегать дублирования кода при написании тестов, мы их параметризируем с помощью `pytest.parameterize`.
Благодаря этому `success_case`-тестов редко бывает больше одного.
Бывает так, что из параметров непонятно, какое поведение тестировалось. Тогда группы параметров можно подписать с
помощью [параметра ids](https://docs.pytest.org/en/latest/example/parametrize.html#different-options-for-test-ids).
## `pytest.mark.xfail` vs `pytest.raises`
Мы используем `pytest.raises`, чтобы показать, что тест проходит только если поднимается исключение.
Мы используем `pytest.mark.xfail` в единственном случае: когда мы написали тесты для новой фичи, а код — нет. Коммит
с такими тестами сломает билд, если их не пометить `pytest.mark.xfail`.
При использовании `xfail` также обязательно указывать параметр `reason`, чтобы облегчить менеджмент помеченных тестов
и не устраивать бардак из бесполезных тестов.
## `mock`
Мы используем фикстуру `mocker` из плагина `pytest-mock` для патчинга походов в сеть и асинхронных задач в легковесных юнит-тестах.
Фикстура которая мокает метод и возвращает `mock` объект должна именоваться с префиксом `mock_`.
Сам мокнутый метод должен иметь префикс `mocked_`.
## `factory_boy`
Мы используем фабрики из `factory_boy` для генерации тестовых Django ORM сущностей и облегчения сетапа тестов.
## Куда класть фабрики и фикстуры
Новые фабрики добавляются по тем же правилам, что и тесты (см. выше) и регистрируются в корневом файле `conftest.py`,
чтобы быть доступными в любом тестовом модуле.
Фикстуры должны располагаться в `conftest.py` соответствующего уровня. Переиспользуемые фикстуры нужно класть как можно выше.
## Фикстуры и фабрики
Чаще всего мы не делаем фикстуры из фабрик.
**Плохо:**
```python
@pytest.fixture()
def n_patients_factory(patient_factory):
def generate_n_patients(patients_num, data_only=False):
if data_only:
return patient_factory.build_dict_batch(patients_num)
return patient_factory.create_batch(patients_num)
```
Такие вещи лучше реализовывать в виде класс-метода на фабрике. Это позволит избежать дублирования усилий.
**Хорошо:**
```python
@classmethod
def create_batch_with_params(cls, patients_num: int, data_only: bool = False) -> List[Patient]:
if data_only:
return cls.build_dict_batch(patients_num)
return cls.create_batch(patients_num)
```
## Уникальные поля моделей в фабриках
### Поля с choices или related
Если у модели есть [UniqueConstraint](https://docs.djangoproject.com/en/3.2/ref/models/constraints/#uniqueconstraint) или поля с `unique=True`, то нужно
добавлять в фабрики [django_get_or_create](https://factoryboy.readthedocs.io/en/stable/orms.html#factory.django.DjangoOptions.django_get_or_create)
### Текстовые поля
Если у модели есть текстовое поле с `unique=True`, то нужно переопределить генерацию этого поля в фабрике
и добавить к значению `step.sequence`. Что бы при создании нескольких
сущностей их значение было гарантированно уникальным.
Например, при помощи такого кода:
`utils/factories.py`:
```python
from factory import Faker
from factory.builder import BuildStep
class UniqueStringMixin(Faker):
def evaluate(self, instance: Any, step: BuildStep, extra: Any) -> str:
value = super().evaluate(instance, step, extra)
return f'{step.sequence} {value}'
```
`app/tests/factories.py`:
```python
from utils.factories import UniqueStringMixin
class CompanyFactory(DjangoModelFactory):
class Meta:
model = Company
title = UniqueStringMixin('word')
```
## Аргументы и ассерты теста
Тесты нужно держать в чистоте и не перегружать зависимости. Поэтому стоит следить за количеством аргументов теста.
Хорошая тестовая функция должна иметь не более 5-6 аргументов в сингнатуре.
В противном случае стоит подумать над разделением проверок.
То же самое касается количества ассертов в тесте.
Большое количество ассертов перегружает тест, снижает его читаемость и полезность.
При инстанцировании фабрик указываем только те аргументы, которые нужны для текущего теста.
Не указываем ненужные и не полагаемся на дефолты в фабрике, если оно нужно в конкретном тесте.
## DRY и KISS
Тесты, как и любой код, должны придерживаться стайлгайдов и лучших практик.
Для этого мы выносим переиспользуемые вещи в фабрики и фикстуры, используем хелперы.
## Как тестировать интеграции со сторонними сервисами
Мы используем интеграционные тесты для проверки подобных сценариев.
Допустим, мы тестируем функцию, присылающую уведомления в Slack — `send_slack_notification`. Тест будет написан так,
чтобы сообщение реально приходило на тестовый канал в Slack.
## Как тестировать функциональность, основанную на фичафлагах
Для фичафлагов мы используем библиотеку [waffle](https://github.com/django-waffle/django-waffle). Библиотека
поставляется с декораторами, которые устанавливают флаг до прогона логики теста. Почитать об их использовании
можно в [документации](https://waffle.readthedocs.io/en/stable/testing/automated.html).
При добавлении новой фичи, закрытой за фичафлагом, пишется функциональный/юнит тест на эту логику с нужным декоратором
с параметром `active=True`. Если на старую логику тест существует, то он оборачивается в декоратор с параметром
`active=False`. Этот подход позволяет быстро приводить в порядок тесты при выпиливании протестированных фичафлагов.
## Решения известных проблем
### Разогрев кеша ContentType
Из-за агрессивного кеширования Django запросов к `ContentType` количество запросов может меняться в зависимости от
способа запуска тестов. Это может отразиться на тестах использующих `django_assert_num_queries`.
В качестве решения при запуске тестов мы принудительно разогреваем кеш:
```python
@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker): # noqa: PT004
with django_db_blocker.unblock():
# Кешируем ContentType для django_assert_num_queries
content_type_ids = ContentType.objects.values_list('id', flat=True)
for ct_id in content_type_ids:
ContentType.objects.get_for_id(ct_id)
```
## Шаблон теста
Мы придерживаемся паттерна ААА в структуре тестов.
Соблюдать эту структуру важно, так как это увеличивает читаемость, а следовательно увеличивает полезность теста.
Однако, если тест сводится к проверке вызова одной функции и не подразумевает большого сетапа, стоит оставить его
однострочником. Например:
```python
def test_format_phone_number():
assert format_phone_number('88005553535') == '+7(800)-555-35-35'
```
[Статья про паттерн AAA](https://medium.com/@pjbgf/title-testing-code-ocd-and-the-aaa-pattern-df453975ab80)
Пример:
```python
@pytest.mark.parametrize(
('param1', 'param2', '...'),
[
(value11, value12, ...),
(value21, value22, ...),
(value31, value32, ...),
(..., ..., ...),
]
)
@pytest.mark.django_db
def test_foo_bar_success_case(param1, param2, ..., foo_factory, ...):
arrange()
act()
assert ...
```
## Как дорабатывать документ
Это не окончательная версия документа. Дорабатывать его можно в репозитории
[на Гитхабе](https://github.com/best-doctor/guides). Идеи — в ишьюс, реализация — в пулл-реквесты.
================================================
FILE: guides/team_guide.md
================================================
# Процессы разработки в BestDoctor
## Спринт
Спринт - это процесс выполнения задач, заранее обсужденных и оцененных командой в течении определенного отрезка.
В разработке BestDoctor спринт идет 5 дней.
## Время на написание кода и встречи
Мы оцениваем задачи и составляем планы на основании того, что разработчик занимается написанием кода в среднем 4
часа в день из 8. Остальные 4 часа отводятся на встречи, общение с коллегами и прочее.
## Daily встречи
Каждый день все команды разработки устраивают 10-15 минутную daily встречу, на которой синхронизируются по поводу
текущего состояния задач:
* рассказывают все ли идет по плану
* если что-то идет не по плану, рассказывают почему так и как можно минимизировать потери
* объясняют какие подводные камни встретились по ходу решения задач, и как их можно побороть.
А также что нужно для этого.
Это нужно для того, чтобы члены команды были синхронизированы, а менеджмент понимал насколько по плану идет
собранный спринт.
Опаздывать на такие встречи (как и на все, в общем то) нельзя. Если член команды не может присутствовать оффлайн,
он уведомляет об этом заранее и его подключают онлайн.
В экстренных случаях, когда *совсем* не получается быть на встрече онлайн или оффлайн, член команды уведомляет об
этом заранее, а потом пишет в чатик команды текстовую версию отчета.
## Грумминг
Пререквизитом для планирования спринта является грумминг.
Грумминг - это обсуждение задач с бизнесовой точки зрения. Команда разработки обсуждает что нужно в той или иной
постановке задачи, какая от этого польза бизнесу и пользователем.
Здесь почти или совсем не обсуждаются технические аспекты - этому место на техническом обсуждении.
В идеале процесс должен быть настроен так, чтобы на момент текущего груминга обсуждались задачи не на ближайший
спринт, а на спринт, следующий за ним. Таким образом, у команды всегда будет бэклог отгрумленных задач, готовых
к техническому обсуждению, с минимум недельным запасом.
## Техническое обсуждение и планирование
Спринт начинается с технического обсуждения заранее отгрумленных задач. На этом этапе команда уже имеет представление
о бизнесовой пользе и необходимости той или иной задачи.
Цель данной встречи в том, чтобы командно обсудить и утвердить техническое решение задачи, а также определить
оценку выполнения. Минимальная оценка выполнения задачи - 1 час.
## Ретроспектива спринта
Каждый спринт оканчивается ретроспективой. Это встреча, на которой команда обсуждает проблемы, возникшие в
течение спринта, предлагает и обсуждает возможные пути решения, и назначает ответственного за решение.
Проблемы собираются на протяжении спринта в таблицу вида:

Пустая таблица ретроспективы в конце спринта редко означает, что спринт прошел идеально - скорее это показатель
того, что заполнение идет не слишком тщательно. Проблемы не фиксируются, процессы не улучшаются. Это плохо.
## One to One встреча
Каждую неделю участники команды разработки проводят One to One встречи со своим тимлидом. Цель данной
встречи - обменяться фидбеком о работе:
* разработчика со стороны тимлида
* о работе команды со стороны разработчика
Готовиться к этой встрече следует как тимлиду, так и разработчику. Для этого у каждого разработчика есть приватная
страница в Notion, доступная только ему и тому, кто проводит One to One.
Запись туда должна осуществляться как в течение рабочего флоу (на подобие ретроспективы туда пишется то, о
чем болит лично у разработчика), так и во время обсуждения проблем - с пометками о возможном решении и
актуальности предыдущих проблем.
## Cинки
Для синхронизации людей из разных команд, работающих в одном направлении (бэкенд, фронтенд, qa) есть специальные
встречи-синки. На них участники направления разработки обсуждают инфраструктурные проблемы, составляют возможные
решения, оформляют это в задачи, которые являются инжинирингом.
## Инжиниринг
Один из пяти спринтовых дней мы занимаемся инженерными задачами. Это разбор технического долга, улучшение
инфраструктуры, починка технических проблем, с которыми мы столкнулись в течение спринта и прочее. Бэклог
инжиниринга формируется на основании записей в специальную таблицу вида:

Также задачи формируются ответственным за направление инжиниринга (фронтенд, бэкенд, qa). На основе этого
составляется инжиниринговый спринт.
## Митапы
BestDoctor проводит внутренние и внешние IT митапы, где разработчики зачитывают доклады по технической тематике
в образовательных целях. На внутренних митапах выступают только разработчики компании, доклады записываются на
видео и доступны на закрытом канале YouTube, с доступом по корпоративной почте. Для внешних приглашаются
сторонние спикеры. Записи докладов доступны на YouTube в открытом доступе.
================================================
FILE: requirements.txt
================================================
rozental-as-a-service==1.1.2
================================================
FILE: setup.cfg
================================================
[opensource_watchman]
type = readings
main_languages = md
features_from_contributors_are_welcome = False
language = ru
gitextract__yh35ou0/ ├── .editorconfig ├── .gitignore ├── .mdlrc.rb ├── .travis.yml ├── .vocabulary ├── LICENSE ├── README.md ├── guides/ │ ├── airflow_guide.md │ ├── api_guide.md │ ├── commit_messages_guide.md │ ├── en/ │ │ └── python_styleguide.md │ ├── frontend-styleguide-luchi.md │ ├── guide_skill_workflow.md │ ├── infra_infosec_guide.md │ ├── opensource_guide.md │ ├── performance_review_guide.md │ ├── python_styleguide.md │ ├── python_test_styleguide.md │ └── team_guide.md ├── requirements.txt └── setup.cfg
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (148K chars).
[
{
"path": ".editorconfig",
"chars": 318,
"preview": "# http://editorconfig.org\n# Inspired by Django .editorconfig file\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4"
},
{
"path": ".gitignore",
"chars": 6,
"preview": ".idea\n"
},
{
"path": ".mdlrc.rb",
"chars": 111,
"preview": "all\nrule 'MD013', :line_length => 120\nrule 'MD024', :allow_different_nesting => true\nrule 'MD007', :indent => 4"
},
{
"path": ".travis.yml",
"chars": 256,
"preview": "language: python\npython:\n - \"3.9\"\ninstall:\n - gem install chef-utils -v 16.6.14\n - gem install mdl\n - pip install -r"
},
{
"path": ".vocabulary",
"chars": 457,
"preview": "# наш словарь\nдатаинженерный\nконфигу\nкрон-джобу\nмерджим\nотгрумленных\nперфоманс-ревью\nпул\nпул-реквест\nпул-реквестами\nпул-"
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2018 BestDoctor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "README.md",
"chars": 876,
"preview": "# `bestdoctor.guides`\n\n[](https://travis-ci.o"
},
{
"path": "guides/airflow_guide.md",
"chars": 4009,
"preview": "# Датаинженерный гайд по работе с Airflow\n\nВ этом гайде собраны договорённости, который мы соблюдаем при работе\nс Apache"
},
{
"path": "guides/api_guide.md",
"chars": 12619,
"preview": "# REST API BestDoctor\n\n## Общие принципы построения API\n\n1. API следует принципу HATEOAS, генерируя необходимые ссылки н"
},
{
"path": "guides/commit_messages_guide.md",
"chars": 10112,
"preview": "# Сообщения коммитов\n\nСообщение коммита – невероятно важная вещь, так как зачастую это единственное место,\nгде можно най"
},
{
"path": "guides/en/python_styleguide.md",
"chars": 11362,
"preview": "# BestDoctor Python styleguide\n\n## Goals\n\nWe are developing a huge product with tons of functionality. Moreover, we need"
},
{
"path": "guides/frontend-styleguide-luchi.md",
"chars": 13163,
"preview": "# Frontend Styleguide LUCHI\n\n> Этот документ - primary source of truth для frontend-стандартов ЛУЧИ.\n>\n> Связанный skill"
},
{
"path": "guides/guide_skill_workflow.md",
"chars": 3402,
"preview": "# Guide-Skill Workflow\n\nЭтот документ описывает, как вести связку `гайд -> производный skill`.\n\n## Статус модели\n\n- Репо"
},
{
"path": "guides/infra_infosec_guide.md",
"chars": 1334,
"preview": "# Гайд по безопасности\n\n## Что это такое\n\nКраткое описание технологий и инструментов, которые мы применяем в работе для "
},
{
"path": "guides/opensource_guide.md",
"chars": 5893,
"preview": "# Правила создания и поддержки opensource-проектов в BestDoctor\n\n## Мотивация\n\nСейчас на <https://github.com/best-doctor"
},
{
"path": "guides/performance_review_guide.md",
"chars": 3492,
"preview": "# Правила проведения перфоманс-ревью в BestDoctor\n\nПерфоманс ревью – встреча, которая проводится с каждым сотрудником it"
},
{
"path": "guides/python_styleguide.md",
"chars": 14163,
"preview": "# Правила написания кода на Python в BestDoctor\n\n## Мотивация\n\nМы разрабатываем здоровенный продукт с кучей функциональн"
},
{
"path": "guides/python_test_styleguide.md",
"chars": 9914,
"preview": "# Гайдлайны для тестов\n\n## Для кого\n\nЭти правила подразумевают, что их читает Python-разработчик, а не тестер или фронте"
},
{
"path": "guides/team_guide.md",
"chars": 4913,
"preview": "# Процессы разработки в BestDoctor\n\n## Спринт\n\nСпринт - это процесс выполнения задач, заранее обсужденных и оцененных ко"
},
{
"path": "requirements.txt",
"chars": 29,
"preview": "rozental-as-a-service==1.1.2\n"
},
{
"path": "setup.cfg",
"chars": 119,
"preview": "[opensource_watchman]\ntype = readings\nmain_languages = md\nfeatures_from_contributors_are_welcome = False\nlanguage = ru\n"
}
]
About this extraction
This page contains the full source code of the best-doctor/guides GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (95.3 KB), approximately 26.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.