pytest-clerk-mock 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_clerk_mock/__init__.py +39 -0
- pytest_clerk_mock/client.py +189 -0
- pytest_clerk_mock/helpers.py +148 -0
- pytest_clerk_mock/models/__init__.py +18 -0
- pytest_clerk_mock/models/auth.py +50 -0
- pytest_clerk_mock/models/organization.py +30 -0
- pytest_clerk_mock/models/user.py +92 -0
- pytest_clerk_mock/plugin.py +215 -0
- pytest_clerk_mock/services/__init__.py +14 -0
- pytest_clerk_mock/services/auth.py +70 -0
- pytest_clerk_mock/services/users.py +495 -0
- pytest_clerk_mock-0.0.2.dist-info/METADATA +251 -0
- pytest_clerk_mock-0.0.2.dist-info/RECORD +16 -0
- pytest_clerk_mock-0.0.2.dist-info/WHEEL +4 -0
- pytest_clerk_mock-0.0.2.dist-info/entry_points.txt +3 -0
- pytest_clerk_mock-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import secrets
|
|
4
|
+
from typing import Any
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from clerk_backend_api.models import ClerkErrors
|
|
9
|
+
from clerk_backend_api.models.clerkerror import ClerkError
|
|
10
|
+
from clerk_backend_api.models.clerkerrors import ClerkErrorsData
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from pytest_clerk_mock.models.organization import MockOrganizationMembershipsResponse
|
|
14
|
+
from pytest_clerk_mock.models.user import MockEmailAddress, MockPhoneNumber, MockUser
|
|
15
|
+
|
|
16
|
+
EMAIL_EXISTS_ERROR_CODE = "form_identifier_exists"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UserNotFoundError(Exception):
|
|
20
|
+
"""Raised when a user is not found."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, user_id: str) -> None:
|
|
23
|
+
self.user_id = user_id
|
|
24
|
+
super().__init__(f"User not found: {user_id}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MockListResponse(BaseModel):
|
|
28
|
+
"""Response wrapper for list operations, matching Clerk SDK structure."""
|
|
29
|
+
|
|
30
|
+
data: list[MockUser] = Field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _generate_id(prefix: str) -> str:
|
|
34
|
+
"""Generate a Clerk-style ID with given prefix."""
|
|
35
|
+
|
|
36
|
+
return f"{prefix}_{secrets.token_hex(12)}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _create_email_exists_error(email: str) -> ClerkErrors:
|
|
40
|
+
"""Create a ClerkErrors exception for duplicate email."""
|
|
41
|
+
|
|
42
|
+
mock_response = MagicMock(spec=httpx.Response)
|
|
43
|
+
mock_response.status_code = 422
|
|
44
|
+
mock_response.text = "That email address is taken."
|
|
45
|
+
mock_response.headers = httpx.Headers({})
|
|
46
|
+
|
|
47
|
+
return ClerkErrors(
|
|
48
|
+
data=ClerkErrorsData(
|
|
49
|
+
errors=[
|
|
50
|
+
ClerkError(
|
|
51
|
+
code=EMAIL_EXISTS_ERROR_CODE,
|
|
52
|
+
message="That email address is taken. Please try another.",
|
|
53
|
+
long_message="That email address is taken. Please try another.",
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
),
|
|
57
|
+
raw_response=mock_response,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class MockUsersClient:
|
|
62
|
+
"""Mock implementation of Clerk's Users API."""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self._users: dict[str, MockUser] = {}
|
|
66
|
+
self._emails: dict[str, str] = {}
|
|
67
|
+
self._memberships: dict[str, MockOrganizationMembershipsResponse] = {}
|
|
68
|
+
|
|
69
|
+
def reset(self) -> None:
|
|
70
|
+
"""Clear all stored users and email mappings."""
|
|
71
|
+
|
|
72
|
+
self._users.clear()
|
|
73
|
+
self._emails.clear()
|
|
74
|
+
self._memberships.clear()
|
|
75
|
+
|
|
76
|
+
def create(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
email_address: list[str] | None = None,
|
|
80
|
+
phone_number: list[str] | None = None,
|
|
81
|
+
username: str | None = None,
|
|
82
|
+
password: str | None = None,
|
|
83
|
+
first_name: str | None = None,
|
|
84
|
+
last_name: str | None = None,
|
|
85
|
+
external_id: str | None = None,
|
|
86
|
+
public_metadata: dict[str, Any] | None = None,
|
|
87
|
+
private_metadata: dict[str, Any] | None = None,
|
|
88
|
+
unsafe_metadata: dict[str, Any] | None = None,
|
|
89
|
+
skip_password_checks: bool = False,
|
|
90
|
+
skip_password_requirement: bool = False,
|
|
91
|
+
totp_secret: str | None = None,
|
|
92
|
+
backup_codes: list[str] | None = None,
|
|
93
|
+
created_at: str | None = None,
|
|
94
|
+
) -> MockUser:
|
|
95
|
+
"""Create a new user."""
|
|
96
|
+
|
|
97
|
+
if email_address:
|
|
98
|
+
for email in email_address:
|
|
99
|
+
if email.lower() in self._emails:
|
|
100
|
+
raise _create_email_exists_error(email)
|
|
101
|
+
|
|
102
|
+
user_id = _generate_id("user")
|
|
103
|
+
email_objects: list[MockEmailAddress] = []
|
|
104
|
+
primary_email_id: str | None = None
|
|
105
|
+
|
|
106
|
+
if email_address:
|
|
107
|
+
for i, email in enumerate(email_address):
|
|
108
|
+
email_id = _generate_id("idn")
|
|
109
|
+
email_obj = MockEmailAddress.create(email=email, email_id=email_id)
|
|
110
|
+
email_objects.append(email_obj)
|
|
111
|
+
self._emails[email.lower()] = user_id
|
|
112
|
+
|
|
113
|
+
if i == 0:
|
|
114
|
+
primary_email_id = email_id
|
|
115
|
+
|
|
116
|
+
phone_objects: list[MockPhoneNumber] = []
|
|
117
|
+
primary_phone_id: str | None = None
|
|
118
|
+
|
|
119
|
+
if phone_number:
|
|
120
|
+
for i, phone in enumerate(phone_number):
|
|
121
|
+
phone_id = _generate_id("idn")
|
|
122
|
+
phone_obj = MockPhoneNumber.create(phone=phone, phone_id=phone_id)
|
|
123
|
+
phone_objects.append(phone_obj)
|
|
124
|
+
|
|
125
|
+
if i == 0:
|
|
126
|
+
primary_phone_id = phone_id
|
|
127
|
+
|
|
128
|
+
user = MockUser(
|
|
129
|
+
id=user_id,
|
|
130
|
+
external_id=external_id,
|
|
131
|
+
primary_email_address_id=primary_email_id,
|
|
132
|
+
primary_phone_number_id=primary_phone_id,
|
|
133
|
+
username=username,
|
|
134
|
+
first_name=first_name,
|
|
135
|
+
last_name=last_name,
|
|
136
|
+
email_addresses=email_objects,
|
|
137
|
+
phone_numbers=phone_objects,
|
|
138
|
+
password_enabled=password is not None,
|
|
139
|
+
public_metadata=public_metadata or {},
|
|
140
|
+
private_metadata=private_metadata or {},
|
|
141
|
+
unsafe_metadata=unsafe_metadata or {},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
self._users[user_id] = user
|
|
145
|
+
|
|
146
|
+
return user
|
|
147
|
+
|
|
148
|
+
def get(self, user_id: str) -> MockUser:
|
|
149
|
+
"""Get a user by ID."""
|
|
150
|
+
|
|
151
|
+
if user_id not in self._users:
|
|
152
|
+
raise UserNotFoundError(user_id)
|
|
153
|
+
|
|
154
|
+
return self._users[user_id]
|
|
155
|
+
|
|
156
|
+
def list(
|
|
157
|
+
self,
|
|
158
|
+
*,
|
|
159
|
+
email_address: list[str] | None = None,
|
|
160
|
+
phone_number: list[str] | None = None,
|
|
161
|
+
external_id: list[str] | None = None,
|
|
162
|
+
username: list[str] | None = None,
|
|
163
|
+
user_id: list[str] | None = None,
|
|
164
|
+
query: str | None = None,
|
|
165
|
+
last_active_at_since: int | None = None,
|
|
166
|
+
limit: int = 10,
|
|
167
|
+
offset: int = 0,
|
|
168
|
+
order_by: str = "-created_at",
|
|
169
|
+
) -> list[MockUser]:
|
|
170
|
+
"""List users with optional filters."""
|
|
171
|
+
|
|
172
|
+
users = list(self._users.values())
|
|
173
|
+
|
|
174
|
+
if email_address:
|
|
175
|
+
email_set = {e.lower() for e in email_address}
|
|
176
|
+
users = [
|
|
177
|
+
u
|
|
178
|
+
for u in users
|
|
179
|
+
if any(e.email_address.lower() in email_set for e in u.email_addresses)
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
if phone_number:
|
|
183
|
+
phone_set = set(phone_number)
|
|
184
|
+
users = [
|
|
185
|
+
u
|
|
186
|
+
for u in users
|
|
187
|
+
if any(p.phone_number in phone_set for p in u.phone_numbers)
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
if external_id:
|
|
191
|
+
ext_id_set = set(external_id)
|
|
192
|
+
users = [u for u in users if u.external_id in ext_id_set]
|
|
193
|
+
|
|
194
|
+
if username:
|
|
195
|
+
username_set = set(username)
|
|
196
|
+
users = [u for u in users if u.username in username_set]
|
|
197
|
+
|
|
198
|
+
if user_id:
|
|
199
|
+
user_id_set = set(user_id)
|
|
200
|
+
users = [u for u in users if u.id in user_id_set]
|
|
201
|
+
|
|
202
|
+
if query:
|
|
203
|
+
query_lower = query.lower()
|
|
204
|
+
users = [
|
|
205
|
+
u
|
|
206
|
+
for u in users
|
|
207
|
+
if (u.first_name and query_lower in u.first_name.lower())
|
|
208
|
+
or (u.last_name and query_lower in u.last_name.lower())
|
|
209
|
+
or (u.username and query_lower in u.username.lower())
|
|
210
|
+
or any(query_lower in e.email_address.lower() for e in u.email_addresses)
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
reverse = order_by.startswith("-")
|
|
214
|
+
sort_key = order_by.lstrip("-+")
|
|
215
|
+
|
|
216
|
+
if sort_key == "created_at":
|
|
217
|
+
users.sort(key=lambda u: u.created_at, reverse=reverse)
|
|
218
|
+
elif sort_key == "updated_at":
|
|
219
|
+
users.sort(key=lambda u: u.updated_at, reverse=reverse)
|
|
220
|
+
|
|
221
|
+
return users[offset : offset + limit]
|
|
222
|
+
|
|
223
|
+
def update(
|
|
224
|
+
self,
|
|
225
|
+
user_id: str,
|
|
226
|
+
*,
|
|
227
|
+
external_id: str | None = None,
|
|
228
|
+
first_name: str | None = None,
|
|
229
|
+
last_name: str | None = None,
|
|
230
|
+
username: str | None = None,
|
|
231
|
+
password: str | None = None,
|
|
232
|
+
primary_email_address_id: str | None = None,
|
|
233
|
+
primary_phone_number_id: str | None = None,
|
|
234
|
+
public_metadata: dict[str, Any] | None = None,
|
|
235
|
+
private_metadata: dict[str, Any] | None = None,
|
|
236
|
+
unsafe_metadata: dict[str, Any] | None = None,
|
|
237
|
+
profile_image_id: str | None = None,
|
|
238
|
+
skip_password_checks: bool = False,
|
|
239
|
+
sign_out_of_other_sessions: bool = False,
|
|
240
|
+
totp_secret: str | None = None,
|
|
241
|
+
backup_codes: list[str] | None = None,
|
|
242
|
+
delete_self_enabled: bool | None = None,
|
|
243
|
+
create_organization_enabled: bool | None = None,
|
|
244
|
+
notify_primary_email_address_changed: bool = False,
|
|
245
|
+
) -> MockUser:
|
|
246
|
+
"""Update a user by ID."""
|
|
247
|
+
|
|
248
|
+
if user_id not in self._users:
|
|
249
|
+
raise UserNotFoundError(user_id)
|
|
250
|
+
|
|
251
|
+
user = self._users[user_id]
|
|
252
|
+
fields = {
|
|
253
|
+
"external_id": external_id,
|
|
254
|
+
"first_name": first_name,
|
|
255
|
+
"last_name": last_name,
|
|
256
|
+
"username": username,
|
|
257
|
+
"primary_email_address_id": primary_email_address_id,
|
|
258
|
+
"primary_phone_number_id": primary_phone_number_id,
|
|
259
|
+
"public_metadata": public_metadata,
|
|
260
|
+
"private_metadata": private_metadata,
|
|
261
|
+
"unsafe_metadata": unsafe_metadata,
|
|
262
|
+
"delete_self_enabled": delete_self_enabled,
|
|
263
|
+
"create_organization_enabled": create_organization_enabled,
|
|
264
|
+
}
|
|
265
|
+
update_data = {k: v for k, v in fields.items() if v is not None}
|
|
266
|
+
|
|
267
|
+
if password is not None:
|
|
268
|
+
update_data["password_enabled"] = True
|
|
269
|
+
|
|
270
|
+
updated_user = user.model_copy(update=update_data)
|
|
271
|
+
self._users[user_id] = updated_user
|
|
272
|
+
|
|
273
|
+
return updated_user
|
|
274
|
+
|
|
275
|
+
def delete(self, user_id: str) -> MockUser:
|
|
276
|
+
"""Delete a user by ID."""
|
|
277
|
+
|
|
278
|
+
if user_id not in self._users:
|
|
279
|
+
raise UserNotFoundError(user_id)
|
|
280
|
+
|
|
281
|
+
user = self._users.pop(user_id)
|
|
282
|
+
|
|
283
|
+
for email in user.email_addresses:
|
|
284
|
+
self._emails.pop(email.email_address.lower(), None)
|
|
285
|
+
|
|
286
|
+
return user
|
|
287
|
+
|
|
288
|
+
def count(
|
|
289
|
+
self,
|
|
290
|
+
*,
|
|
291
|
+
email_address: list[str] | None = None,
|
|
292
|
+
phone_number: list[str] | None = None,
|
|
293
|
+
external_id: list[str] | None = None,
|
|
294
|
+
username: list[str] | None = None,
|
|
295
|
+
user_id: list[str] | None = None,
|
|
296
|
+
query: str | None = None,
|
|
297
|
+
) -> int:
|
|
298
|
+
"""Count users matching the filters."""
|
|
299
|
+
|
|
300
|
+
users = self.list(
|
|
301
|
+
email_address=email_address,
|
|
302
|
+
phone_number=phone_number,
|
|
303
|
+
external_id=external_id,
|
|
304
|
+
username=username,
|
|
305
|
+
user_id=user_id,
|
|
306
|
+
query=query,
|
|
307
|
+
limit=999999,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return len(users)
|
|
311
|
+
|
|
312
|
+
def set_organization_memberships(
|
|
313
|
+
self,
|
|
314
|
+
user_id: str,
|
|
315
|
+
memberships: MockOrganizationMembershipsResponse,
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Configure organization memberships for a user."""
|
|
318
|
+
|
|
319
|
+
self._memberships[user_id] = memberships
|
|
320
|
+
|
|
321
|
+
def get_organization_memberships(
|
|
322
|
+
self,
|
|
323
|
+
user_id: str,
|
|
324
|
+
*,
|
|
325
|
+
limit: int | None = 10,
|
|
326
|
+
offset: int | None = 0,
|
|
327
|
+
) -> MockOrganizationMembershipsResponse:
|
|
328
|
+
"""Get organization memberships for a user (sync version)."""
|
|
329
|
+
|
|
330
|
+
return self._memberships.get(
|
|
331
|
+
user_id,
|
|
332
|
+
MockOrganizationMembershipsResponse(data=[], total_count=0),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
async def get_organization_memberships_async(
|
|
336
|
+
self,
|
|
337
|
+
user_id: str,
|
|
338
|
+
*,
|
|
339
|
+
limit: int | None = 10,
|
|
340
|
+
offset: int | None = 0,
|
|
341
|
+
) -> MockOrganizationMembershipsResponse:
|
|
342
|
+
"""Get organization memberships for a user (async version)."""
|
|
343
|
+
|
|
344
|
+
return self.get_organization_memberships(user_id, limit=limit, offset=offset)
|
|
345
|
+
|
|
346
|
+
async def create_async(
|
|
347
|
+
self,
|
|
348
|
+
*,
|
|
349
|
+
email_address: list[str] | None = None,
|
|
350
|
+
phone_number: list[str] | None = None,
|
|
351
|
+
username: str | None = None,
|
|
352
|
+
password: str | None = None,
|
|
353
|
+
first_name: str | None = None,
|
|
354
|
+
last_name: str | None = None,
|
|
355
|
+
external_id: str | None = None,
|
|
356
|
+
public_metadata: dict[str, Any] | None = None,
|
|
357
|
+
private_metadata: dict[str, Any] | None = None,
|
|
358
|
+
unsafe_metadata: dict[str, Any] | None = None,
|
|
359
|
+
skip_password_checks: bool = False,
|
|
360
|
+
skip_password_requirement: bool = False,
|
|
361
|
+
totp_secret: str | None = None,
|
|
362
|
+
backup_codes: list[str] | None = None,
|
|
363
|
+
created_at: str | None = None,
|
|
364
|
+
) -> MockUser:
|
|
365
|
+
"""Async version of create."""
|
|
366
|
+
|
|
367
|
+
return self.create(
|
|
368
|
+
email_address=email_address,
|
|
369
|
+
phone_number=phone_number,
|
|
370
|
+
username=username,
|
|
371
|
+
password=password,
|
|
372
|
+
first_name=first_name,
|
|
373
|
+
last_name=last_name,
|
|
374
|
+
external_id=external_id,
|
|
375
|
+
public_metadata=public_metadata,
|
|
376
|
+
private_metadata=private_metadata,
|
|
377
|
+
unsafe_metadata=unsafe_metadata,
|
|
378
|
+
skip_password_checks=skip_password_checks,
|
|
379
|
+
skip_password_requirement=skip_password_requirement,
|
|
380
|
+
totp_secret=totp_secret,
|
|
381
|
+
backup_codes=backup_codes,
|
|
382
|
+
created_at=created_at,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
async def get_async(self, user_id: str) -> MockUser:
|
|
386
|
+
"""Async version of get."""
|
|
387
|
+
|
|
388
|
+
return self.get(user_id)
|
|
389
|
+
|
|
390
|
+
async def list_async(
|
|
391
|
+
self,
|
|
392
|
+
*,
|
|
393
|
+
email_address: list[str] | None = None,
|
|
394
|
+
phone_number: list[str] | None = None,
|
|
395
|
+
external_id: list[str] | None = None,
|
|
396
|
+
username: list[str] | None = None,
|
|
397
|
+
user_id: list[str] | None = None,
|
|
398
|
+
query: str | None = None,
|
|
399
|
+
last_active_at_since: int | None = None,
|
|
400
|
+
limit: int = 10,
|
|
401
|
+
offset: int = 0,
|
|
402
|
+
order_by: str = "-created_at",
|
|
403
|
+
) -> MockListResponse:
|
|
404
|
+
"""Async version of list.
|
|
405
|
+
|
|
406
|
+
Returns a MockListResponse with .data attribute to match Clerk SDK behavior.
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
users = self.list(
|
|
410
|
+
email_address=email_address,
|
|
411
|
+
phone_number=phone_number,
|
|
412
|
+
external_id=external_id,
|
|
413
|
+
username=username,
|
|
414
|
+
user_id=user_id,
|
|
415
|
+
query=query,
|
|
416
|
+
last_active_at_since=last_active_at_since,
|
|
417
|
+
limit=limit,
|
|
418
|
+
offset=offset,
|
|
419
|
+
order_by=order_by,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
return MockListResponse(data=users)
|
|
423
|
+
|
|
424
|
+
async def update_async(
|
|
425
|
+
self,
|
|
426
|
+
user_id: str,
|
|
427
|
+
*,
|
|
428
|
+
external_id: str | None = None,
|
|
429
|
+
first_name: str | None = None,
|
|
430
|
+
last_name: str | None = None,
|
|
431
|
+
username: str | None = None,
|
|
432
|
+
password: str | None = None,
|
|
433
|
+
primary_email_address_id: str | None = None,
|
|
434
|
+
primary_phone_number_id: str | None = None,
|
|
435
|
+
public_metadata: dict[str, Any] | None = None,
|
|
436
|
+
private_metadata: dict[str, Any] | None = None,
|
|
437
|
+
unsafe_metadata: dict[str, Any] | None = None,
|
|
438
|
+
profile_image_id: str | None = None,
|
|
439
|
+
skip_password_checks: bool = False,
|
|
440
|
+
sign_out_of_other_sessions: bool = False,
|
|
441
|
+
totp_secret: str | None = None,
|
|
442
|
+
backup_codes: list[str] | None = None,
|
|
443
|
+
delete_self_enabled: bool | None = None,
|
|
444
|
+
create_organization_enabled: bool | None = None,
|
|
445
|
+
notify_primary_email_address_changed: bool = False,
|
|
446
|
+
) -> MockUser:
|
|
447
|
+
"""Async version of update."""
|
|
448
|
+
|
|
449
|
+
return self.update(
|
|
450
|
+
user_id,
|
|
451
|
+
external_id=external_id,
|
|
452
|
+
first_name=first_name,
|
|
453
|
+
last_name=last_name,
|
|
454
|
+
username=username,
|
|
455
|
+
password=password,
|
|
456
|
+
primary_email_address_id=primary_email_address_id,
|
|
457
|
+
primary_phone_number_id=primary_phone_number_id,
|
|
458
|
+
public_metadata=public_metadata,
|
|
459
|
+
private_metadata=private_metadata,
|
|
460
|
+
unsafe_metadata=unsafe_metadata,
|
|
461
|
+
profile_image_id=profile_image_id,
|
|
462
|
+
skip_password_checks=skip_password_checks,
|
|
463
|
+
sign_out_of_other_sessions=sign_out_of_other_sessions,
|
|
464
|
+
totp_secret=totp_secret,
|
|
465
|
+
backup_codes=backup_codes,
|
|
466
|
+
delete_self_enabled=delete_self_enabled,
|
|
467
|
+
create_organization_enabled=create_organization_enabled,
|
|
468
|
+
notify_primary_email_address_changed=notify_primary_email_address_changed,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
async def delete_async(self, user_id: str) -> MockUser:
|
|
472
|
+
"""Async version of delete."""
|
|
473
|
+
|
|
474
|
+
return self.delete(user_id)
|
|
475
|
+
|
|
476
|
+
async def count_async(
|
|
477
|
+
self,
|
|
478
|
+
*,
|
|
479
|
+
email_address: list[str] | None = None,
|
|
480
|
+
phone_number: list[str] | None = None,
|
|
481
|
+
external_id: list[str] | None = None,
|
|
482
|
+
username: list[str] | None = None,
|
|
483
|
+
user_id: list[str] | None = None,
|
|
484
|
+
query: str | None = None,
|
|
485
|
+
) -> int:
|
|
486
|
+
"""Async version of count."""
|
|
487
|
+
|
|
488
|
+
return self.count(
|
|
489
|
+
email_address=email_address,
|
|
490
|
+
phone_number=phone_number,
|
|
491
|
+
external_id=external_id,
|
|
492
|
+
username=username,
|
|
493
|
+
user_id=user_id,
|
|
494
|
+
query=query,
|
|
495
|
+
)
|