rootpy-client 1.31.1__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.
Files changed (63) hide show
  1. rootpy/__init__.py +393 -0
  2. rootpy/_registry_loader.py +122 -0
  3. rootpy/accounts.py +554 -0
  4. rootpy/attach.py +243 -0
  5. rootpy/auth.py +310 -0
  6. rootpy/cache.py +190 -0
  7. rootpy/client.py +1702 -0
  8. rootpy/commands.py +444 -0
  9. rootpy/data/enums.json +1 -0
  10. rootpy/data/message_schemas.json +1 -0
  11. rootpy/data/messages.json +1 -0
  12. rootpy/data/rpc_services.json +1 -0
  13. rootpy/data/services.json +1 -0
  14. rootpy/discovery.py +578 -0
  15. rootpy/dm_member.py +305 -0
  16. rootpy/domain_managers.py +677 -0
  17. rootpy/emoji.py +129 -0
  18. rootpy/enums.py +315 -0
  19. rootpy/events.py +153 -0
  20. rootpy/exceptions.py +687 -0
  21. rootpy/features.py +949 -0
  22. rootpy/gateway.py +1477 -0
  23. rootpy/generated_rpc_registry.py +18 -0
  24. rootpy/highlevel.py +1472 -0
  25. rootpy/host.py +594 -0
  26. rootpy/identifiers.py +119 -0
  27. rootpy/media.py +43 -0
  28. rootpy/media_bootstrap.py +111 -0
  29. rootpy/models.py +944 -0
  30. rootpy/object_api.py +606 -0
  31. rootpy/packet_schemas.py +181 -0
  32. rootpy/packets.py +176 -0
  33. rootpy/pagination.py +31 -0
  34. rootpy/permissions.py +237 -0
  35. rootpy/presence.py +173 -0
  36. rootpy/protocol.py +164 -0
  37. rootpy/py.typed +0 -0
  38. rootpy/raw_api.py +244 -0
  39. rootpy/responses.py +110 -0
  40. rootpy/root_exception.py +306 -0
  41. rootpy/service_facade.py +32 -0
  42. rootpy/services/__init__.py +27 -0
  43. rootpy/services/assets.py +503 -0
  44. rootpy/services/calls.py +1411 -0
  45. rootpy/services/communities.py +735 -0
  46. rootpy/services/community_admin.py +1036 -0
  47. rootpy/services/direct_messages.py +524 -0
  48. rootpy/services/discovery.py +204 -0
  49. rootpy/services/messages.py +811 -0
  50. rootpy/services/users.py +440 -0
  51. rootpy/stats.py +208 -0
  52. rootpy/structured_api.py +956 -0
  53. rootpy/structured_registry.py +61 -0
  54. rootpy/transport.py +403 -0
  55. rootpy/typed_events.py +496 -0
  56. rootpy/unread.py +546 -0
  57. rootpy/users.py +187 -0
  58. rootpy/validation.py +145 -0
  59. rootpy_client-1.31.1.dist-info/METADATA +157 -0
  60. rootpy_client-1.31.1.dist-info/RECORD +63 -0
  61. rootpy_client-1.31.1.dist-info/WHEEL +5 -0
  62. rootpy_client-1.31.1.dist-info/licenses/LICENSE +21 -0
  63. rootpy_client-1.31.1.dist-info/top_level.txt +1 -0
rootpy/__init__.py ADDED
@@ -0,0 +1,393 @@
1
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
2
+
3
+ try:
4
+ __version__ = _pkg_version("rootpy-client")
5
+ except PackageNotFoundError: # running from a source tree, not installed
6
+ __version__ = "0.0.0.dev0"
7
+
8
+ from .client import RootClient
9
+ from .commands import (
10
+ Command,
11
+ CommandArgumentError,
12
+ CommandCheckFailure,
13
+ CommandError,
14
+ CommandNotFound,
15
+ Context,
16
+ parse_channel_id,
17
+ parse_channel_mention,
18
+ parse_user_id,
19
+ parse_user_mention,
20
+ )
21
+ from .emoji import normalize_reaction, parse_emoji_mention
22
+ from .events import (
23
+ MessageAction,
24
+ ChannelAction,
25
+ CommunityLeaveReason,
26
+ CallDetachedEvent,
27
+ ChannelDeletedEvent,
28
+ ChannelEvent,
29
+ CommandErrorEvent,
30
+ CommandEvent,
31
+ CommunityDeletedEvent,
32
+ CommunityEvent,
33
+ CommunityLeaveEvent,
34
+ MessageEvent,
35
+ NotificationEvent,
36
+ ReadyEvent,
37
+ )
38
+ from .exceptions import (
39
+ ErrorInfo,
40
+ get_error_info,
41
+ format_root_error,
42
+ UsernameLookupAuthenticationRequired,
43
+ GrpcStatus,
44
+ GrpcCancelled,
45
+ GrpcUnknown,
46
+ GrpcInvalidArgument,
47
+ GrpcDeadlineExceeded,
48
+ GrpcNotFound,
49
+ GrpcAlreadyExists,
50
+ GrpcPermissionDenied,
51
+ GrpcResourceExhausted,
52
+ GrpcFailedPrecondition,
53
+ GrpcAborted,
54
+ GrpcOutOfRange,
55
+ GrpcUnimplemented,
56
+ GrpcInternal,
57
+ GrpcUnavailable,
58
+ GrpcDataLoss,
59
+ GrpcUnauthenticated,
60
+ AccountAlreadyExists,
61
+ AuthenticationError,
62
+ EmailAlreadyExists,
63
+ GrpcWebError,
64
+ RootError,
65
+ SignUpError,
66
+ TurnstileRequired,
67
+ UsernameAlreadyExists,
68
+ )
69
+ from .identifiers import (
70
+ normalize_root_guid,
71
+ root_guid_datetime,
72
+ root_guid_type,
73
+ )
74
+ from .media import (
75
+ MediaBackendInfo,
76
+ media_backend_info,
77
+ )
78
+ from .media_bootstrap import (
79
+ ensure_ffmpeg,
80
+ ensure_media_dependencies,
81
+ )
82
+ from .models import (
83
+ AuthenticationSession,
84
+ CallSession,
85
+ Channel,
86
+ ChannelGroup,
87
+ Community,
88
+ CommunityExtended,
89
+ CurrentUser,
90
+ Message,
91
+ MessageAttachment,
92
+ MessageSendResult,
93
+ )
94
+ from .packets import (
95
+ PacketType,
96
+ SocketPacket,
97
+ )
98
+ from .permissions import (
99
+ PermissionName,
100
+ AccessRule,
101
+ ChannelOverlay,
102
+ ChannelPermission,
103
+ CommunityPermission,
104
+ ActionNotApplicable,
105
+ CallActionError,
106
+ ChannelPermissions,
107
+ MissingPermissions,
108
+ PermissionStateUnavailable,
109
+ )
110
+ from .services.assets import AssetService
111
+ from .services.direct_messages import (
112
+ DirectMessage,
113
+ DirectMessageService,
114
+ )
115
+ from .dm_member import DMMemberService
116
+ from .users import User
117
+
118
+
119
+ __all__ = [
120
+ "__version__",
121
+
122
+ # Client
123
+ "RootClient",
124
+ "ErrorInfo",
125
+ "get_error_info",
126
+
127
+ # Multi-account hosting
128
+ "MultiClientHost",
129
+ "HostedAccount",
130
+ "Outcome",
131
+ "format_root_error",
132
+
133
+ # Reading unread
134
+ "UnreadReader",
135
+ "UnreadChannel",
136
+ "is_unread",
137
+
138
+ # Presence and attach lifetime
139
+ "AttachHold",
140
+ "PresenceWatch",
141
+ "effective_presence",
142
+
143
+ # Commands
144
+ "Command",
145
+ "CommandArgumentError",
146
+ "CommandCheckFailure",
147
+ "CommandError",
148
+ "CommandNotFound",
149
+ "Context",
150
+ "parse_channel_id",
151
+ "parse_channel_mention",
152
+ "parse_user_id",
153
+ "parse_user_mention",
154
+
155
+ "normalize_reaction",
156
+ "parse_emoji_mention",
157
+
158
+ # Events
159
+ "CallDetachedEvent",
160
+ "ChannelDeletedEvent",
161
+ "ChannelEvent",
162
+ "CommandErrorEvent",
163
+ "CommandEvent",
164
+ "CommunityDeletedEvent",
165
+ "CommunityEvent",
166
+ "CommunityLeaveEvent",
167
+ "MessageEvent",
168
+ "NotificationEvent",
169
+ "ReadyEvent",
170
+
171
+ # Exceptions
172
+ "RootError",
173
+ "AuthenticationError",
174
+ "GrpcWebError",
175
+ "TurnstileRequired",
176
+ "SignUpError",
177
+ "AccountAlreadyExists",
178
+ "UsernameAlreadyExists",
179
+ "EmailAlreadyExists",
180
+
181
+ "normalize_root_guid",
182
+ "root_guid_datetime",
183
+ "root_guid_type",
184
+
185
+ # Models
186
+ "AuthenticationSession",
187
+ "CallSession",
188
+ "Channel",
189
+ "ChannelGroup",
190
+ "Community",
191
+ "CommunityExtended",
192
+ "CurrentUser",
193
+ "Message",
194
+ "MessageAttachment",
195
+ "MessageSendResult",
196
+ "User",
197
+
198
+ # Packets
199
+ "PacketType",
200
+ "SocketPacket",
201
+
202
+ # Permissions
203
+ "ActionNotApplicable",
204
+ # The base the other three share. Exported so `except CallActionError`
205
+ # catches a refused mute/kick in one clause -- the subclasses were
206
+ # reachable before, their base was not.
207
+ "CallActionError",
208
+ "ChannelPermissions",
209
+ "MissingPermissions",
210
+ "PermissionStateUnavailable",
211
+
212
+ # Direct messages
213
+ "DirectMessage",
214
+ "DirectMessageService",
215
+ "DMMemberService",
216
+ "DirectMessageError",
217
+
218
+ "AssetService",
219
+
220
+ # Voice / media
221
+ "AudioPlayback",
222
+ "IceInfo",
223
+ "MediaBackendInfo",
224
+ "media_backend_info",
225
+ "ensure_ffmpeg",
226
+ "ensure_media_dependencies",
227
+ "RawAPI",
228
+ "RawService",
229
+ "RawMethod",
230
+ "RawRpcResult",
231
+ "AccessRule",
232
+ "ChannelOverlay",
233
+ "ChannelPermission",
234
+ "CommunityPermission",
235
+ "CommunityAdminService",
236
+ "GrpcStatus",
237
+ "GrpcCancelled",
238
+ "GrpcUnknown",
239
+ "GrpcInvalidArgument",
240
+ "GrpcDeadlineExceeded",
241
+ "GrpcNotFound",
242
+ "GrpcAlreadyExists",
243
+ "GrpcPermissionDenied",
244
+ "GrpcResourceExhausted",
245
+ "GrpcFailedPrecondition",
246
+ "GrpcAborted",
247
+ "GrpcOutOfRange",
248
+ "GrpcUnimplemented",
249
+ "GrpcInternal",
250
+ "GrpcUnavailable",
251
+ "GrpcDataLoss",
252
+ "GrpcUnauthenticated",
253
+ "MessageAction",
254
+ "ChannelAction",
255
+ "CommunityLeaveReason",
256
+ "PermissionName",
257
+ "AttrDict",
258
+ "EnumValue",
259
+ "StructuredAPI",
260
+ "StructuredMethod",
261
+ "StructuredResult",
262
+ "StructuredService",
263
+ "UsernameLookupAuthenticationRequired",
264
+ "RootServiceFacade",
265
+ "RoleManager",
266
+ "MemberManager",
267
+ "CommunityFileManager",
268
+ "LogManager",
269
+ "CommunityAppManager",
270
+ "VoiceAdminManager",
271
+ "FriendshipGroupManager",
272
+ ]
273
+
274
+
275
+ from .services.community_admin import CommunityAdminService
276
+
277
+
278
+ from .service_facade import RootServiceFacade
279
+ from .domain_managers import (
280
+ RoleManager,
281
+ MemberManager,
282
+ CommunityFileManager,
283
+ LogManager,
284
+ CommunityAppManager,
285
+ VoiceAdminManager,
286
+ FriendshipGroupManager,
287
+ )
288
+
289
+ from .object_api import CommunityManager, PermissionManager
290
+
291
+ from .pagination import AsyncPager, Page
292
+
293
+ from .events import EventErrorEvent
294
+
295
+ from .exceptions import HttpStatusError, BadRequest, Unauthorized, Forbidden, HttpNotFound, Conflict, PayloadTooLarge, RateLimited, ServerError, BadGateway, ServiceUnavailable, GatewayTimeout, RetryExhausted, DirectMessageError
296
+
297
+ from .root_exception import (
298
+ RootExceptionInfo,
299
+ ValidationError,
300
+ decode_root_exception,
301
+ decode_root_exception_header,
302
+ )
303
+
304
+ from .enums import (
305
+ ErrorCodeType,
306
+ PacketErrorCode,
307
+ ContentFlagReason,
308
+ NotificationType,
309
+ MessageType,
310
+ ChannelType,
311
+ UserOnlineStatus,
312
+ )
313
+
314
+ from .packet_schemas import PACKET_SCHEMAS, decode_packet
315
+
316
+ from .events import ChannelActivity
317
+
318
+ from .typed_events import (
319
+ BlockEvent,
320
+ ChannelEvent as TypedChannelEvent,
321
+ CommunityEvent as TypedCommunityEvent,
322
+ FriendEvent,
323
+ MemberEvent,
324
+ MemberRoleEvent,
325
+ ReactionEvent,
326
+ RoleEvent,
327
+ )
328
+
329
+ from .cache import LRUCache, StateCache
330
+
331
+ from .validation import (
332
+ DEFAULT_PICTURE_HEX,
333
+ USERNAME_MAX_LENGTH,
334
+ USERNAME_MIN_LENGTH,
335
+ USERNAME_RULE,
336
+ normalize_hex_colour,
337
+ validate_nickname,
338
+ validate_username,
339
+ )
340
+
341
+ # The generated RPC registries are big and slow to import (~770 ms cold, about
342
+ # half of total import time), and most programs never touch them. They load on
343
+ # first use instead -- `from rootpy import StructuredAPI` still works, it just
344
+ # pays the cost at that moment rather than on every import of the package.
345
+ _LAZY_EXPORTS = {
346
+ "RawAPI": "raw_api",
347
+ "RawMethod": "raw_api",
348
+ "RawRpcResult": "raw_api",
349
+ "RawService": "raw_api",
350
+ "AttrDict": "structured_api",
351
+ "EnumValue": "structured_api",
352
+ "StructuredAPI": "structured_api",
353
+ "StructuredMethod": "structured_api",
354
+ "StructuredResult": "structured_api",
355
+ "StructuredService": "structured_api",
356
+ # Voice: rootpy.services.calls pulls in the media support modules, so it
357
+ # is kept off the import path too.
358
+ "AudioPlayback": "services.calls",
359
+ "IceInfo": "services.calls",
360
+ }
361
+
362
+
363
+ def __getattr__(name):
364
+ module_name = _LAZY_EXPORTS.get(name)
365
+ if module_name is None:
366
+ raise AttributeError(f"module 'rootpy' has no attribute {name!r}")
367
+ import importlib
368
+
369
+ module = importlib.import_module(f".{module_name}", __name__)
370
+ value = getattr(module, name)
371
+ globals()[name] = value # cache it, so this happens once
372
+ return value
373
+
374
+ from .host import HostedAccount, MultiClientHost, Outcome
375
+
376
+ from .models import DetailedMember, UserProfile
377
+
378
+ from .stats import EndpointStats, TransportStats
379
+
380
+ from .unread import UnreadChannel, UnreadReader, is_unread
381
+
382
+ from .attach import AttachHold
383
+
384
+ from .presence import PresenceWatch, effective_presence
385
+
386
+ from .services.assets import Asset, AssetLink
387
+
388
+ from .accounts import (
389
+ AccountFactory,
390
+ AlreadyCreatedError,
391
+ CreatedAccount,
392
+ TurnstileChallenge,
393
+ )
@@ -0,0 +1,122 @@
1
+ """Lazy loading for the generated protocol registries.
2
+
3
+ The registries are large -- 842 message definitions, 842 raw schemas (the same
4
+ key set, one field table per message), 31 services -- and most programs touch
5
+ only a handful of them. Storing them as
6
+ Python literals meant every field of every message was parsed and built into
7
+ dicts before the first request could be sent.
8
+
9
+ They now live as JSON under ``rootpy/data/``. :class:`LazyRegistry` is a
10
+ read-only mapping that behaves exactly like the dict it replaced but defers
11
+ reading and decoding the file until something actually looks a key up. Import
12
+ stays cheap; the cost is paid once, on first use, by whoever needs it.
13
+
14
+ ``DerivedRegistry`` is the same idea for indexes that can be computed from
15
+ another registry rather than stored (see ``SIMPLE_MESSAGES``).
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from pathlib import Path
22
+ from typing import Any, Callable, Dict, Iterator, Mapping
23
+
24
+ _DATA_DIR = Path(__file__).resolve().parent / "data"
25
+
26
+
27
+ class LazyRegistry(Mapping):
28
+ """A read-only mapping backed by a JSON file, loaded on first access.
29
+
30
+ Implements the full :class:`~collections.abc.Mapping` protocol, so it is a
31
+ drop-in for the plain dicts these registries used to be: subscripting,
32
+ ``in``, ``.get()``, ``.items()``, ``len()`` and iteration all work.
33
+ """
34
+
35
+ __slots__ = ("_filename", "_data")
36
+
37
+ def __init__(self, filename: str) -> None:
38
+ self._filename = filename
39
+ self._data: Dict[str, Any] | None = None
40
+
41
+ def _load(self) -> Dict[str, Any]:
42
+ data = self._data
43
+ if data is None:
44
+ path = _DATA_DIR / self._filename
45
+ try:
46
+ with path.open("r", encoding="utf-8") as handle:
47
+ data = json.load(handle)
48
+ except FileNotFoundError as exc: # pragma: no cover - packaging bug
49
+ raise RuntimeError(
50
+ f"rootpy data file {self._filename!r} is missing. The "
51
+ "package was probably installed without its data files; "
52
+ "reinstall rootpy."
53
+ ) from exc
54
+ self._data = data
55
+ return data
56
+
57
+ # -- Mapping protocol ------------------------------------------------
58
+ def __getitem__(self, key: str) -> Any:
59
+ return self._load()[key]
60
+
61
+ def __iter__(self) -> Iterator[str]:
62
+ return iter(self._load())
63
+
64
+ def __len__(self) -> int:
65
+ return len(self._load())
66
+
67
+ def __contains__(self, key: object) -> bool:
68
+ return key in self._load()
69
+
70
+ def __repr__(self) -> str:
71
+ state = "unloaded" if self._data is None else f"{len(self._data)} entries"
72
+ return f"<LazyRegistry {self._filename} ({state})>"
73
+
74
+ # -- conveniences ----------------------------------------------------
75
+ @property
76
+ def loaded(self) -> bool:
77
+ """True once the backing file has been read."""
78
+ return self._data is not None
79
+
80
+ def preload(self) -> "LazyRegistry":
81
+ """Force the load now, e.g. to keep it off a latency-sensitive path."""
82
+ self._load()
83
+ return self
84
+
85
+
86
+ class DerivedRegistry(Mapping):
87
+ """A mapping computed from another registry the first time it is used."""
88
+
89
+ __slots__ = ("_builder", "_data")
90
+
91
+ def __init__(self, builder: Callable[[], Dict[str, Any]]) -> None:
92
+ self._builder = builder
93
+ self._data: Dict[str, Any] | None = None
94
+
95
+ def _load(self) -> Dict[str, Any]:
96
+ if self._data is None:
97
+ self._data = self._builder()
98
+ return self._data
99
+
100
+ def __getitem__(self, key: str) -> Any:
101
+ return self._load()[key]
102
+
103
+ def __iter__(self) -> Iterator[str]:
104
+ return iter(self._load())
105
+
106
+ def __len__(self) -> int:
107
+ return len(self._load())
108
+
109
+ def __contains__(self, key: object) -> bool:
110
+ return key in self._load()
111
+
112
+ def __repr__(self) -> str:
113
+ state = "unloaded" if self._data is None else f"{len(self._data)} entries"
114
+ return f"<DerivedRegistry ({state})>"
115
+
116
+ @property
117
+ def loaded(self) -> bool:
118
+ return self._data is not None
119
+
120
+ def preload(self) -> "DerivedRegistry":
121
+ self._load()
122
+ return self