Перейти к содержанию

Сервис пользователей

Модуль users_service предоставляет абстракции и реализации для работы с пользователями в сервисном слое приложения.

Особенности

  • Абстрактный базовый класс ABCUserService с полным набором методов для работы с пользователями
  • Реализация UserService с бизнес-логикой работы с пользователями
  • Интеграция с Unit of Work для управления транзакциями
  • Поддержка хеширования паролей через абстракцию IPasswordHasher
  • Полностью асинхронный API

Документация API

ABCUserService

Абстрактный базовый класс, определяющий интерфейс для работы с пользователями.

Bases: ABC

Абстрактный базовый класс сервиса пользователей.

Определяет интерфейс для работы с пользователями, который должен быть реализован в конкретных классах-наследниках.

Source code in src/service_layer/users_service.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
class ABCUserService(abc.ABC):
    """Абстрактный базовый класс сервиса пользователей.

    Определяет интерфейс для работы с пользователями,
    который должен быть реализован в конкретных классах-наследниках.
    """

    @abc.abstractmethod
    async def register_user(
        self,
        first_name: str,
        last_name: str,
        email: str,
        username: str,
        password: str,
    ) -> User:
        """Создаёт нового пользователя и сохраняет его в репозиторий.

        Args:
            first_name: Имя пользователя.
            last_name: Фамилия пользователя.
            email: Email пользователя.
            username: Логин пользователя.
            password: Пароль пользователя.

        Returns:
            User: Созданный объект пользователя.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def login(self, email: str, password: str) -> bool:
        """Проверяет логин пользователя и обновляет время последнего входа.

        Args:
            email: Email пользователя.
            password: Пароль пользователя.

        Returns:
            bool: True, если аутентификация успешна, иначе False.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def change_password(self, user_id: uuid.UUID, new_password: str) -> None:
        """Меняет пароль пользователя.

        Args:
            user_id: ID пользователя.
            new_password: Новый пароль.

        Raises:
            ValueError: Если пользователь с указанным ID не найден.
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def activate_user(self, user_id: uuid.UUID) -> None:
        """Активирует пользователя.

        Args:
            user_id: ID пользователя.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def deactivate_user(self, user_id: uuid.UUID) -> None:
        """Деактивирует пользователя.

        Args:
            user_id: ID пользователя.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def verify_email(self, user_id: uuid.UUID) -> None:
        """Подтверждает email пользователя.

        Args:
            user_id: ID пользователя.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def get_user_by_email(self, email: str) -> User | None:
        """Находит пользователя по email.

        Args:
            email: Email пользователя.

        Returns:
            User | None: Найденный объект пользователя или None.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def get_user_by_id(self, user_id: uuid.UUID) -> User | None:
        """Находит пользователя по ID.

        Args:
            user_id: ID пользователя.

        Returns:
            User | None: Найденный объект пользователя или None.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def get_user_by_username(self, username: str) -> User | None:
        """Находит пользователя по username.

        Args:
            username: Логин пользователя.

        Returns:
            User | None: Найденный объект пользователя или None.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

    @abc.abstractmethod
    async def list_users(self, only_active: bool = False) -> list[User]:
        """Возвращает список пользователей.

        Args:
            only_active: Если True, возвращает только активных пользователей.

        Returns:
            list[User]: Список пользователей.

        Raises:
            NotImplementedError: Если метод не переопределён в подклассе.

        """
        raise NotImplementedError

activate_user(user_id: uuid.UUID) -> None abstractmethod async

Активирует пользователя.

Parameters:

Name Type Description Default
user_id UUID

ID пользователя.

required

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
75
76
77
78
79
80
81
82
83
84
85
86
@abc.abstractmethod
async def activate_user(self, user_id: uuid.UUID) -> None:
    """Активирует пользователя.

    Args:
        user_id: ID пользователя.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

change_password(user_id: uuid.UUID, new_password: str) -> None abstractmethod async

Меняет пароль пользователя.

Parameters:

Name Type Description Default
user_id UUID

ID пользователя.

required
new_password str

Новый пароль.

required

Raises:

Type Description
ValueError

Если пользователь с указанным ID не найден.

NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@abc.abstractmethod
async def change_password(self, user_id: uuid.UUID, new_password: str) -> None:
    """Меняет пароль пользователя.

    Args:
        user_id: ID пользователя.
        new_password: Новый пароль.

    Raises:
        ValueError: Если пользователь с указанным ID не найден.
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

deactivate_user(user_id: uuid.UUID) -> None abstractmethod async

Деактивирует пользователя.

Parameters:

Name Type Description Default
user_id UUID

ID пользователя.

required

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
88
89
90
91
92
93
94
95
96
97
98
99
@abc.abstractmethod
async def deactivate_user(self, user_id: uuid.UUID) -> None:
    """Деактивирует пользователя.

    Args:
        user_id: ID пользователя.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

get_user_by_email(email: str) -> User | None abstractmethod async

Находит пользователя по email.

Parameters:

Name Type Description Default
email str

Email пользователя.

required

Returns:

Type Description
User | None

User | None: Найденный объект пользователя или None.

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
@abc.abstractmethod
async def get_user_by_email(self, email: str) -> User | None:
    """Находит пользователя по email.

    Args:
        email: Email пользователя.

    Returns:
        User | None: Найденный объект пользователя или None.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

get_user_by_id(user_id: uuid.UUID) -> User | None abstractmethod async

Находит пользователя по ID.

Parameters:

Name Type Description Default
user_id UUID

ID пользователя.

required

Returns:

Type Description
User | None

User | None: Найденный объект пользователя или None.

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
@abc.abstractmethod
async def get_user_by_id(self, user_id: uuid.UUID) -> User | None:
    """Находит пользователя по ID.

    Args:
        user_id: ID пользователя.

    Returns:
        User | None: Найденный объект пользователя или None.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

get_user_by_username(username: str) -> User | None abstractmethod async

Находит пользователя по username.

Parameters:

Name Type Description Default
username str

Логин пользователя.

required

Returns:

Type Description
User | None

User | None: Найденный объект пользователя или None.

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
@abc.abstractmethod
async def get_user_by_username(self, username: str) -> User | None:
    """Находит пользователя по username.

    Args:
        username: Логин пользователя.

    Returns:
        User | None: Найденный объект пользователя или None.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

list_users(only_active: bool = False) -> list[User] abstractmethod async

Возвращает список пользователей.

Parameters:

Name Type Description Default
only_active bool

Если True, возвращает только активных пользователей.

False

Returns:

Type Description
list[User]

list[User]: Список пользователей.

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
@abc.abstractmethod
async def list_users(self, only_active: bool = False) -> list[User]:
    """Возвращает список пользователей.

    Args:
        only_active: Если True, возвращает только активных пользователей.

    Returns:
        list[User]: Список пользователей.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

login(email: str, password: str) -> bool abstractmethod async

Проверяет логин пользователя и обновляет время последнего входа.

Parameters:

Name Type Description Default
email str

Email пользователя.

required
password str

Пароль пользователя.

required

Returns:

Name Type Description
bool bool

True, если аутентификация успешна, иначе False.

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@abc.abstractmethod
async def login(self, email: str, password: str) -> bool:
    """Проверяет логин пользователя и обновляет время последнего входа.

    Args:
        email: Email пользователя.
        password: Пароль пользователя.

    Returns:
        bool: True, если аутентификация успешна, иначе False.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

register_user(first_name: str, last_name: str, email: str, username: str, password: str) -> User abstractmethod async

Создаёт нового пользователя и сохраняет его в репозиторий.

Parameters:

Name Type Description Default
first_name str

Имя пользователя.

required
last_name str

Фамилия пользователя.

required
email str

Email пользователя.

required
username str

Логин пользователя.

required
password str

Пароль пользователя.

required

Returns:

Name Type Description
User User

Созданный объект пользователя.

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@abc.abstractmethod
async def register_user(
    self,
    first_name: str,
    last_name: str,
    email: str,
    username: str,
    password: str,
) -> User:
    """Создаёт нового пользователя и сохраняет его в репозиторий.

    Args:
        first_name: Имя пользователя.
        last_name: Фамилия пользователя.
        email: Email пользователя.
        username: Логин пользователя.
        password: Пароль пользователя.

    Returns:
        User: Созданный объект пользователя.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

verify_email(user_id: uuid.UUID) -> None abstractmethod async

Подтверждает email пользователя.

Parameters:

Name Type Description Default
user_id UUID

ID пользователя.

required

Raises:

Type Description
NotImplementedError

Если метод не переопределён в подклассе.

Source code in src/service_layer/users_service.py
101
102
103
104
105
106
107
108
109
110
111
112
@abc.abstractmethod
async def verify_email(self, user_id: uuid.UUID) -> None:
    """Подтверждает email пользователя.

    Args:
        user_id: ID пользователя.

    Raises:
        NotImplementedError: Если метод не переопределён в подклассе.

    """
    raise NotImplementedError

UserService

Реализация сервиса пользователей с бизнес-логикой.

Bases: ABCUserService

Сервисный слой для работы с пользователями (бизнес-логика).

Source code in src/service_layer/users_service.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
class UserService(ABCUserService):
    """Сервисный слой для работы с пользователями (бизнес-логика)."""

    def __init__(self, uow: AbstractUnitOfWork, hasher: IPasswordHasher) -> None:
        """Инициализация сервиса.

        Args:
            uow: Unit of Work для управления транзакциями и репозиториями.
            hasher: Сервис для хеширования и проверки паролей.

        """
        self.uow = uow
        self.hasher = hasher

    async def register_user(
        self,
        first_name: str,
        last_name: str,
        email: str,
        username: str,
        password: str,
    ) -> User:
        hashed = self.hasher.hash_password(password)
        user = User(
            user_id=None,
            first_name=first_name,
            last_name=last_name,
            email=email,
            username=username,
            hashed_password=hashed,
            _hasher=self.hasher,
        )

        async with self.uow as uow:
            await uow.users.add(user)
            await uow.commit()
        return user

    async def remove_user(self, user_id: uuid.UUID) -> None:
        """Удаляет пользователя по ID.

        Args:
            user_id: ID пользователя.

        Raises:
            Exception: Если произошла ошибка при удалении пользователя.

        """
        async with self.uow as uow:
            await uow.users.remove(user_id)
            await uow.commit()

    async def login(self, email: str, password: str) -> bool:
        async with self.uow as uow:
            user = await uow.users.get_by_email(email)
            if not user:
                return False
            if not self.hasher.verify_password(password, user.hashed_password):
                return False
            await uow.users.update_login_time(user.id)
            await uow.commit()
            return True

    async def change_password(self, user_id: uuid.UUID, new_password: str) -> None:
        hashed = self.hasher.hash_password(new_password)
        async with self.uow as uow:
            user = await uow.users.get_by_id(user_id)
            if not user:
                raise ValueError('User not found')
            user.hashed_password = hashed
            await uow.users.update(user)
            await uow.commit()

    async def activate_user(self, user_id: uuid.UUID) -> None:
        async with self.uow as uow:
            await uow.users.activate(user_id)
            await uow.commit()

    async def deactivate_user(self, user_id: uuid.UUID) -> None:
        async with self.uow as uow:
            await uow.users.deactivate(user_id)
            await uow.commit()

    async def verify_email(self, user_id: uuid.UUID) -> None:
        async with self.uow as uow:
            await uow.users.verify_email(user_id)
            await uow.commit()

    async def get_user_by_email(self, email: str) -> User | None:
        async with self.uow as uow:
            return await uow.users.get_by_email(email)

    async def get_user_by_id(self, user_id: uuid.UUID) -> User | None:
        async with self.uow as uow:
            return await uow.users.get_by_id(user_id)

    async def get_user_by_username(self, username: str) -> User | None:
        async with self.uow as uow:
            return await uow.users.get_by_username(username)

    async def list_users(self, only_active: bool = False) -> list[User]:
        async with self.uow as uow:
            return await uow.users.list_all(only_active=only_active)

__init__(uow: AbstractUnitOfWork, hasher: IPasswordHasher) -> None

Инициализация сервиса.

Parameters:

Name Type Description Default
uow AbstractUnitOfWork

Unit of Work для управления транзакциями и репозиториями.

required
hasher IPasswordHasher

Сервис для хеширования и проверки паролей.

required
Source code in src/service_layer/users_service.py
182
183
184
185
186
187
188
189
190
191
def __init__(self, uow: AbstractUnitOfWork, hasher: IPasswordHasher) -> None:
    """Инициализация сервиса.

    Args:
        uow: Unit of Work для управления транзакциями и репозиториями.
        hasher: Сервис для хеширования и проверки паролей.

    """
    self.uow = uow
    self.hasher = hasher

remove_user(user_id: uuid.UUID) -> None async

Удаляет пользователя по ID.

Parameters:

Name Type Description Default
user_id UUID

ID пользователя.

required

Raises:

Type Description
Exception

Если произошла ошибка при удалении пользователя.

Source code in src/service_layer/users_service.py
217
218
219
220
221
222
223
224
225
226
227
228
229
async def remove_user(self, user_id: uuid.UUID) -> None:
    """Удаляет пользователя по ID.

    Args:
        user_id: ID пользователя.

    Raises:
        Exception: Если произошла ошибка при удалении пользователя.

    """
    async with self.uow as uow:
        await uow.users.remove(user_id)
        await uow.commit()

Примеры использования

Регистрация нового пользователя

# Создание экземпляра сервиса
uow = SqlAlchemyUnitOfWork(session_factory=session_factory, repo_factory=repo_factory)
hasher = BcryptPasswordHasher()
user_service = UserService(uow=uow, hasher=hasher)

# Регистрация пользователя
user = await user_service.register_user(
    first_name="Иван",
    last_name="Иванов",
    email="ivan@example.com",
    username="ivanov",
    password="securepassword123"
)

Аутентификация пользователя

# Аутентификация
is_authenticated = await user_service.login(
    email="ivan@example.com",
    password="securepassword123"
)

if is_authenticated:
    print("Аутентификация успешна")
else:
    print("Неверные учетные данные")

Получение пользователя по ID

user = await user_service.get_user_by_id(user_id=user_id)
if user:
    print(f"Найден пользователь: {user.email}")

Лучшие практики

  1. Использование Unit of Work: Все операции с базой данных выполняются в контексте Unit of Work, что обеспечивает атомарность операций.
  2. Валидация данных: Входные данные должны быть валидированы до вызова методов сервиса.
  3. Обработка ошибок: Методы могут вызывать исключения, которые должны быть обработаны на уровне API.
  4. Кэширование: Для часто запрашиваемых данных рассмотрите возможность добавления кэширования.
  5. Асинхронность: Все методы асинхронные и должны вызываться с использованием await.

Интеграция

Модуль интегрируется с:

  • FastAPI приложениями
  • SQLAlchemy ORM через Unit of Work
  • Системой аутентификации
  • Системой хеширования паролей

Ограничения

  • Требует наличия настроенной базы данных
  • Зависит от корректной работы Unit of Work
  • Не поддерживает пакетные операции
  • Нет встроенной поддержки пагинации для списков пользователей

Дополнительные возможности

Модуль может быть расширен для поддержки:

  • Восстановления пароля
  • Двухфакторной аутентификации
  • Управления ролями и разрешениями
  • Аудит-лога операций с пользователями

См. также