Этот документ определяет стандарты написания кода для нашего проекта. Его цель - обеспечить единообразие, читаемость и поддерживаемость кодовой базы. Все участники проекта должны следовать этим правилам при написании нового кода и при рефакторинге существующего.
Основой нашего стиля кодирования является Google Python Style Guide. Если конкретная особенность не описана в данном документе, то ориентироваться следует на линтер Ruff. Если линтер не учитывает ситуацию, вызывающую вопросы, следует обратиться к руководству Google.
Рекомендации в этом документе являются обязательными. Если они противоречат руководству Google, приоритет имеют правила, описанные здесь.
При разработке, настоятельно рекомендуется использование плагинов соответствующих инструментов в Вашей IDE. Информация по их установке указана в описании соответствующего инструмента.
Мы используем Ruff в качестве основного инструмента для линтинга и форматирования кода. Основные настройки Ruff включают:
- Целевая версия Python: 3.8
- Длина строки: 80 символов
- Ширина отступа: 4 пробела
- Стиль кавычек: одинарные кавычки
- Стиль отступов: пробелы
- Автоматическое определение окончания строки
Ruff настроен на проверку широкого спектра правил, включая стандартные проверки PEP 8, проверки импортов, документации, аннотаций типов, безопасности и различные стилистические проверки.
Для запуска проверки кода с помощью Ruff используйте следующую команду:
ruff check .
Для автоматического исправления некоторых ошибок используйте:
ruff check --fix .
Для подробного ознакомление с функционалом Ruff и как работают его команды, можно ознакомиться на странице официальной документации Ruff
Для удобства проверки при написании кода, рекомендуем использовать плагины, которые для официальных IDE, предварительно настроив их. Список поддерживаемых сред разработки и их конфигурации описана на официальной странице
Справку по каждому из правил, с которыми работает Ruff можно найти на этой странице
Игнорирование предупреждений линтера допустимо в следующих случаях:
- Когда следование правилу ухудшает читаемость кода или превышена длина первой строки докуаментации, а описать локаничнее не удаётся.
Например для документации для этого в конце docstring используются аннотации: noqa: E501, W505
E501 - line-too-long
W505 - doc-line-too-long
"""Getting all network interfaces and its extra database. VERY LONG FIRST STRING
Args:
is_need_filter (Optional[bool]): Flag indicating if it is
necessary to filter interfaces or not.
Returns:
List of serialized dictionaries of interface's data.
""" # noqa: E501, W505 It is not possible to describe the operation of this method more concisely
- Когда правило не применимо в конкретном контексте.
- Когда с точки зрения здравого смысла это имеет значение, либо этого требует конкретная реализация.
Всегда добавляйте комментарий, объясняющий, почему вы игнорируете предупреждение:
# Ignoring the warning about an unused variable, as it is needed for API compatibility
unused_variable = some_function() # noqa: F841
Если вы считаете, что какое-то правило линтера неуместно или требует изменения, следуйте этому процессу:
- Создайте issue в репозитории проекта с описанием проблемы и предлагаемым решением.
- Приведите примеры кода, демонстрирующие проблему.
- Объясните, почему изменение правила улучшит кодовую базу.
- Дождитесь обсуждения и консенсуса команды.
- После одобрения, внесите изменения в конфигурацию Ruff и обновите этот документ.
mypy
– это статический анализатор типов для Python, который помогает выявлять ошибки, связанные с несовместимостью типов, до выполнения кода. Мы используем mypy для строгого контроля типизации и повышения качества кода. Ниже приведены настройки mypy, используемые в проекте:
Конфигурация mypy находится в файле mypy.ini в корневом каталоге проекта и содержит следующие параметры:
[mypy]
warn_return_any = True
warn_unused_configs = True
strict_optional = True
ignore_missing_imports = True
disallow_any_unimported = True
check_untyped_defs = True
disallow_untyped_defs = True
no_implicit_optional = True
show_error_codes = True
warn_unused_ignores = True
exclude = venv|data|openvair/libs/messaging/protocol\.py
warn_return_any = True: Предупреждает, если функция объявлена как возвращающая значение любого типа (Any). Это помогает идентифицировать места в коде, где типизация возвращаемого значения недостаточно строгая.
warn_unused_configs = True: Предупреждает о неиспользуемых или неправильных настройках в конфигурационном файле. Этот параметр помогает следить за чистотой и корректностью конфигурации.
strict_optional = True: Включает строгую проверку использования типов, которые могут быть None. Это гарантирует, что возможное значение None в типах будет корректно обрабатываться, что позволяет избежать потенциальных ошибок, связанных с несоответствием типов.
ignore_missing_imports = True: Игнорирует ошибки, связанные с отсутствием аннотаций типов в сторонних библиотеках. Это полезно, если проект использует библиотеки, не содержащие встроенных типов, и позволяет избежать ложных предупреждений.
disallow_any_unimported = True: Запрещает использование значений типа Any, импортированных из модулей, для которых отсутствует информация о типах. Это помогает сделать код более надежным и минимизировать риск ошибок, связанных с неопределенными типами.
check_untyped_defs = True: Анализирует функции и методы без аннотаций типов, выявляя возможные проблемы в них. Этот параметр позволяет обнаруживать ошибки в коде, даже если для некоторых функций не указаны аннотации типов.
disallow_untyped_defs = True: Запрещает объявления функций и методов без аннотаций типов. Это одно из ключевых правил, направленное на обеспечение строгой типизации всего кода, что значительно повышает его надежность и читаемость.
no_implicit_optional = True: Запрещает неявное использование Optional для аргументов, которые могут быть None. Вместо этого необходимо явно указывать Optional, что делает код более читаемым и понятным.
show_error_codes = True: Показывает коды ошибок вместе с сообщениями mypy. Это позволяет быстро найти подробную информацию об ошибке и понять, как ее исправить, ссылаясь на официальную документацию mypy.
warn_unused_ignores = True: Предупреждает, если комментарии # type: ignore используются без необходимости. Это помогает поддерживать чистоту в коде и избегать ненужных исключений из проверки типов.
exclude = venv|data|openvair/libs/messaging/protocol.py: Исключает из проверки определенные файлы и каталоги, такие как виртуальное окружение (venv), данные (data) и конкретный файл (openvair/libs/messaging/protocol.py). Это позволяет сосредоточиться на проверке только тех частей кода, которые имеют значение для проекта и исключить проверку, частей, требующих рефакторинга или где динамическая типизация оправдана.
Для запуска проверки кода с использованием настроек, описанных выше, активируйте venv проекта и выполните команду:
mypy .
Иногда необходимо игнорировать некоторые предупреждения mypy, например, при работе с кодом сторонних библиотек или в случаях использования динамических типов. Для этого можно использовать комментарий # type: ignore:
from external_module import some_function # type: ignore
result = some_function() # Игнорируем предупреждение об отсутствии аннотаций типов
Если вы игнорируете конкретное предупреждение, всегда добавляйте пояснение к комментарию:
from external_module import some_function # type: ignore[attr-defined] # Игнорируем, так как библиотека не предоставляет аннотацию типа
Для удобства использования mypy можно интегрировать его с различными средами разработки (IDE), такими как PyCharm или VSCode. Это позволяет автоматически проверять типы при написании кода. Подробную информацию по интеграции можно найти в официальной документации mypy.
Если вы считаете, что какое-то правило или настройка mypy неуместны для проекта, следуйте процессу обсуждения, аналогичному тому, что используется для линтера Ruff:
Создайте issue в репозитории проекта с описанием проблемы и предложением изменения. Приведите примеры кода, демонстрирующие проблему. Объясните, почему изменение конфигурации mypy улучшит кодовую базу. Дождитесь обсуждения и консенсуса команды. После одобрения внесите изменения в конфигурацию mypy и обновите этот документ.
Максимальная длина строки - 80 символов. Это правило помогает сохранять код читаемым и удобным для просмотра на большинстве экранов.
Если строка превышает этот лимит, используйте переносы. Предпочтительно использовать круглые скобки для переноса строк.
Пример правильного переноса:
message = (
f"Storage status is {storage_status} "
f"but must be in {available_statuses}"
)
В некоторых случаях допустимо игнорировать это правило:
Для длинных строк с URL или путями к файлам. Для строк в многострочных строковых литералах (docstrings или комментарии). Если вы считаете, что строку нельзя разбить без потери читаемости, обсудите это с командой. В исключительных случаях можно использовать комментарий # noqa: E501 для игнорирования предупреждения линтера:
very_long_variable_name = some_long_function_call(arg1, arg2, ...) # noqa: E501
Помните, что злоупотребление игнорированием правил может привести к снижению качества кода. Всегда стремитесь к соблюдению ограничения в 80 символов, когда это возможно.
Используйте 4 пробела для отступов. Не используйте табуляцию.
def long_function_name(
var_one: str,
var_two: str,
var_three: str,
) -> None:
print(var_one)
В случаае, если сигнатура метода не умещается в длину строки, то нужно использовать запятую после последнего аргумента и размещать каждый аргумент на новой строке:
foo = long_function_name(
var_one: str,
var_two: int,
var_three: Dict,
var_four: List,
) -> None:
Импорты должны быть на отдельных строках:
# Правильно:
import os
import sys
# Неправильно:
import os, sys
Импорты всегда помещаются в начало файла, сразу после комментариев к модулю и docstrings, и перед глобальными переменными и константами.
Импорты должны быть сгруппированы в следующем порядке:
Стандартные библиотеки Связанные сторонние импорты Локальные импорты приложения/библиотеки Вы должны поставить пустую строку между каждой группой импортов.
Исключения из текущего слоя (например, сервисного) импортируются как просто exceptions. Исключения из пакетов иного слоя или отдельной библиотеки должны получать псевдоним с суффиксом _exc:
from openvair.libs.messaging import exceptions as msg_exc from openvair.modules.storage.service_layer import exceptions
Если из пакета импортируется множество классов, то перечисляйте их в круглых скобках, с новой строки после открытия скобки и закрытием после перечисления последнего класса:
from package import (
Class1,
Class2,
Class3,
)
Для форматирования строк используйте исключительно f-строки. Не используйте метод format() или форматирование через символ %.
# Правильно:
name = "Alice"
age = 30
message = f"Hello, {name}! You are {age} years old."
# Неправильно:
message = "Hello, {}! You are {} years old.".format(name, age)
message = "Hello, %s! You are %d years old." % (name, age)
Для регулярных выражений используйте r-строки:
import re
pattern = r"\d{3}-\d{2}-\d{4}"
ssn = "123-45-6789"
if re.match(pattern, ssn):
print("Valid SSN format")
При вызове метода суперкласса используйте super() без передачи наименования класса:
class StorageServiceLayerManager(BackgroundTasks):
def __init__(self):
super().__init__()
class BridgePortGroup(BasePortGroup):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Обязательно используйте аннотации типов (type hints) для аргументов функций, методов и возвращаемых значений. Используйте библиотеку typing для сложных типов.
Для словарей и подобных структур не описывайте вложенные типы для входящих аргументов, но обязательно описывайте их для возвращаемых значений. Если могут быть разные типы данных, предусмотрите это в аннотации.
from typing import Dict, Union
def create_partition(self, data: Dict) -> Dict[str, int]:
# Реализация метода
def process_data(self, data: Dict) -> Dict[str, Union[str, int]]:
# Реализация метода
Имена классов должны следовать соглашению CapWords (также известному как PascalCase):
class MyClass:
pass
class MyABCClass:
pass
Имена методов и функций должны быть написаны в нижнем регистре, с подчеркиваниями между словами:
def my_function():
pass
class MyClass:
def my_method(self):
pass
Имена переменных также должны быть написаны в нижнем регистре, с подчеркиваниями между словами:
my_variable = 5
user_name = "John"
Константы должны быть написаны заглавными буквами с подчеркиваниями между словами:
MAX_OVERFLOW = 100
TOTAL = 0
Комментарии должны быть полными предложениями. Если комментарий - фраза или предложение, первое слово должно быть написано с заглавной буквы, если это не имя переменной, начинающееся с нижнего регистра.
Docstring в Python-модулях используется для документирования различных элементов: модулей, классов, функций и методов. Существуют определенные правила и рекомендации по их оформлению.
- Форматирование с использованием 4 пробелов для отступов.
Docstring модуля (.py файл) должен находиться в самом начале файла. Он должен содержать:
- Краткое описание модуля и его назначение.
- Подробное описание назначения модуля и особенностей его работы
- Описание основных классов, перечислений и других сущностей, определенных в модуле.
При формировании docstring модуля, переносы строк должны быть сделаны таким образом, чтобы каждый абзац (логическая группа предложений) был на отдельной строке. В случае переноса содержание новой строки должно выделяться табуляцией
"""Module for managing the Block Devices Service Layer.
This module defines the `BlockDevicesServiceLayerManager` class, which serves as
the main entry point for handling block device-related operations in the service
layer. The class is responsible for interacting with the domain layer and the
event store to perform various tasks, such as retrieving the host IQN and
managing ISCSI sessions.
The module also includes the `ISCSIInterfaceStatus` enum, which defines the
possible status values for an ISCSI interface, and the `CreateInterfaceInfo`
namedtuple, which is used to store information about a new ISCSI interface.
Classes:
ISCSIInterfaceStatus: Enum representing the possible status values for an
ISCSI interface.
CreateInterfaceInfo: Namedtuple for storing information about a new ISCSI
interface.
BlockDevicesServiceLayerManager: Manager class for handling block devices
service layer operations.
"""
Для классов, функций и методов docstring должен быть оформлен в соответствии с Google-стилем:
- Первая строка должна содержать краткое описание. Одной строкой, которая не превышает допустимую длинну строки. В случае если есть превышение на небольшое количесво символов, допустимо добавить данную строку в исключения для ruff. Но рекомендуется найти лаконичное описание для первой строки, чтобы она вписывалась в формат.
- Далее следует более подробное описание. Необходимо придерживаться длины строки, если слово не умещается, то корректно его перенести на новую строку и продолжить описание там
- Секция "Attributes" должна содержать описание атрибутов класса. Она должна быть отделена пустой строкой от предыдущего описания.
- Секции "Args", "Returns" и "Raises" должны содержать описание аргументов, возвращаемого значения и исключений соответственно. Каждая секция должна быть отделена пустой строкой.
- При описании атрибутов, аргументов и потенциальных исключений, обратите внимание на переносы, для удобства читаемости кода, перенесённая строка продолжающаяя описание конкретного элемента дополняется ещё одни отступом.
Пример:
# Некорректно
"""
...
Attributes:
domain_rpc (RabbitRPCClient): RPC client for communicating with the
domain layer.
service_layer_rpc (RabbitRPCClient): RPC client for communicating
with the API service layer.
"""
# Корректно
"""
...
Attributes:
domain_rpc (RabbitRPCClient): RPC client for communicating with the
domain layer.
service_layer_rpc (RabbitRPCClient): RPC client for communicating
with the API service layer.
"""
class BlockDevicesServiceLayerManager(BackgroundTasks):
"""Manager class for handling block devices service layer operations.
This class is responsible for coordinating the interactions between the
service layer and the domain layer, as well as the event store, to manage
block device-related operations.
Attributes:
domain_rpc (RabbitRPCClient): RPC client for communicating with the
domain layer.
service_layer_rpc (RabbitRPCClient): RPC client for communicating
with the API service layer.
uow (SqlAlchemyUnitOfWork): Unit of work for managing database
transactions.
event_store (EventCrud): Event store for handling block device-related
events.
"""
def __init__(self):
"""Initialize the BlockDevicesServiceLayerManager.
This method sets up the necessary components for the
BlockDevicesServiceLayerManager, including the RabbitMQ RPC clients,
the unit of work, and the event store.
"""
super().__init__()
self.domain_rpc: RabbitRPCClient = Protocol(client=True)(
SERVICE_LAYER_DOMAIN_QUEUE_NAME
)
# ...
def lip_scan(self) -> Dict:
"""Perform a Fibre Channel LIP (Loop Initialization Procedure) scan.
This method is responsible for initiating a Fibre Channel LIP scan on
the host system. It communicates with the domain layer to execute the
LIP scan and returns the result.
Returns:
Dict: The result of the Fibre Channel LIP scan.
Raises:
FibreChannelLipScanException: If an error occurs during the LIP scan
process.
"""
# ...