osu.py 2.2.0__tar.gz → 2.2.2__tar.gz

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 (50) hide show
  1. {osu_py-2.2.0/osu.py.egg-info → osu_py-2.2.2}/PKG-INFO +1 -1
  2. {osu_py-2.2.0 → osu_py-2.2.2}/osu/__init__.py +1 -1
  3. {osu_py-2.2.0 → osu_py-2.2.2}/osu/asyncio/client.py +5 -1
  4. {osu_py-2.2.0 → osu_py-2.2.2}/osu/client.py +8 -1
  5. {osu_py-2.2.0 → osu_py-2.2.2}/osu/enums.py +2 -4
  6. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/achievement.py +9 -9
  7. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/beatmap.py +92 -92
  8. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/beatmapset_event.py +25 -25
  9. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/build.py +25 -26
  10. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/chat.py +12 -12
  11. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/comment.py +23 -23
  12. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/current_user_attributes.py +24 -24
  13. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/discussion.py +36 -36
  14. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/event.py +36 -36
  15. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/forum.py +28 -28
  16. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/group.py +8 -8
  17. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/kudosu.py +11 -11
  18. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/match.py +26 -26
  19. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/multiplayer.py +48 -48
  20. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/news.py +9 -9
  21. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/notification.py +62 -62
  22. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/ranking.py +11 -12
  23. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/score.py +49 -50
  24. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/seasonal_background.py +5 -6
  25. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/user.py +108 -108
  26. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/wiki.py +11 -12
  27. {osu_py-2.2.0 → osu_py-2.2.2}/osu/util.py +16 -2
  28. {osu_py-2.2.0 → osu_py-2.2.2/osu.py.egg-info}/PKG-INFO +1 -1
  29. {osu_py-2.2.0 → osu_py-2.2.2}/LICENSE +0 -0
  30. {osu_py-2.2.0 → osu_py-2.2.2}/MANIFEST.in +0 -0
  31. {osu_py-2.2.0 → osu_py-2.2.2}/README.rst +0 -0
  32. {osu_py-2.2.0 → osu_py-2.2.2}/osu/asyncio/__init__.py +0 -0
  33. {osu_py-2.2.0 → osu_py-2.2.2}/osu/asyncio/http.py +0 -0
  34. {osu_py-2.2.0 → osu_py-2.2.2}/osu/auth.py +0 -0
  35. {osu_py-2.2.0 → osu_py-2.2.2}/osu/constants.py +0 -0
  36. {osu_py-2.2.0 → osu_py-2.2.2}/osu/exceptions.py +0 -0
  37. {osu_py-2.2.0 → osu_py-2.2.2}/osu/http.py +0 -0
  38. {osu_py-2.2.0 → osu_py-2.2.2}/osu/notification.py +0 -0
  39. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/__init__.py +0 -0
  40. {osu_py-2.2.0 → osu_py-2.2.2}/osu/objects/scope.py +0 -0
  41. {osu_py-2.2.0 → osu_py-2.2.2}/osu/path.py +0 -0
  42. {osu_py-2.2.0 → osu_py-2.2.2}/osu/results.py +0 -0
  43. {osu_py-2.2.0 → osu_py-2.2.2}/osu.py.egg-info/SOURCES.txt +0 -0
  44. {osu_py-2.2.0 → osu_py-2.2.2}/osu.py.egg-info/dependency_links.txt +0 -0
  45. {osu_py-2.2.0 → osu_py-2.2.2}/osu.py.egg-info/requires.txt +0 -0
  46. {osu_py-2.2.0 → osu_py-2.2.2}/osu.py.egg-info/top_level.txt +0 -0
  47. {osu_py-2.2.0 → osu_py-2.2.2}/pyproject.toml +0 -0
  48. {osu_py-2.2.0 → osu_py-2.2.2}/requirements.txt +0 -0
  49. {osu_py-2.2.0 → osu_py-2.2.2}/setup.cfg +0 -0
  50. {osu_py-2.2.0 → osu_py-2.2.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osu.py
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: API Wrapper for osu!api v2 written in Python.
5
5
  Home-page: https://github.com/Sheepposu/osu.py
6
6
  Author: Sheepposu
@@ -14,4 +14,4 @@ from .util import (
14
14
  from .results import *
15
15
 
16
16
 
17
- __version__ = "2.2.0"
17
+ __version__ = "2.2.2"
@@ -1449,6 +1449,9 @@ class AsynchronousClient:
1449
1449
  User default mode will be used if not specified.
1450
1450
 
1451
1451
  key: Optional[:class:`str`]
1452
+ **DEPRECATED**
1453
+ It's recommended to prefix usernames with @ instead of setting key
1454
+
1452
1455
  Type of user passed in url parameter. Can be either `id` or `username`
1453
1456
  to limit lookup by their respective type. Passing empty or invalid
1454
1457
  value will result in id lookup followed by username lookup if not found.
@@ -1466,7 +1469,8 @@ class AsynchronousClient:
1466
1469
  `user_achievements`.
1467
1470
  """
1468
1471
  mode = parse_enum_args(mode)
1469
- return User(await self.http.make_request(Path.get_user(user, mode), key=key))
1472
+ user = f"@{user}" if key is not None and key.lower() == "username" else user
1473
+ return User(await self.http.make_request(Path.get_user(user, mode)))
1470
1474
 
1471
1475
  async def get_users(self, ids: Sequence[int]) -> List[UserCompact]:
1472
1476
  """
@@ -1424,6 +1424,9 @@ class Client:
1424
1424
  User default mode will be used if not specified.
1425
1425
 
1426
1426
  key: Optional[:class:`str`]
1427
+ **DEPRECATED**
1428
+ It's recommended to prefix usernames with @ instead of setting key
1429
+
1427
1430
  Type of user passed in url parameter. Can be either `id` or `username`
1428
1431
  to limit lookup by their respective type. Passing empty or invalid
1429
1432
  value will result in id lookup followed by username lookup if not found.
@@ -1441,7 +1444,8 @@ class Client:
1441
1444
  `user_achievements`.
1442
1445
  """
1443
1446
  mode = parse_enum_args(mode)
1444
- return User(self.http.make_request(Path.get_user(user, mode), key=key))
1447
+ user = f"@{user}" if key is not None and key.lower() == "username" else user
1448
+ return User(self.http.make_request(Path.get_user(user, mode)))
1445
1449
 
1446
1450
  def get_users(self, ids: Sequence[int]) -> List[UserCompact]:
1447
1451
  """
@@ -1461,6 +1465,9 @@ class Client:
1461
1465
  Includes attributes: country, cover, groups, statistics_rulesets.
1462
1466
  """
1463
1467
  res = self.http.make_request(Path.get_users(), **{"ids[]": ids})
1468
+ import json
1469
+ with open("users.json", "w", encoding="utf-8") as f:
1470
+ json.dump(res["users"], f)
1464
1471
  return list(map(UserCompact, res["users"]))
1465
1472
 
1466
1473
  def get_wiki_page(self, locale: str, path: str) -> WikiPage:
@@ -460,8 +460,7 @@ class GameModeStr(Enum):
460
460
  CATCH = "fruits"
461
461
  MANIA = "mania"
462
462
 
463
- @staticmethod
464
- def get_int_equivalent(gamemode: Enum) -> IntEnum:
463
+ def get_int_equivalent(self: "GameModeStr") -> "GameModeInt":
465
464
  return GameModeInt[gamemode.name]
466
465
 
467
466
 
@@ -485,8 +484,7 @@ class GameModeInt(IntEnum):
485
484
  CATCH = 2
486
485
  MANIA = 3
487
486
 
488
- @staticmethod
489
- def get_str_equivalent(gamemode: IntEnum) -> Enum:
487
+ def get_str_equivalent(self: "GameModeInt") -> GameModeStr:
490
488
  return GameModeStr[gamemode.name]
491
489
 
492
490
 
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from ..util import prettify, get_optional
3
+ from ..util import prettify, get_optional, get_required
4
4
  from ..enums import GameModeStr
5
5
 
6
6
 
@@ -40,15 +40,15 @@ class Achievement:
40
40
  )
41
41
 
42
42
  def __init__(self, data):
43
- self.icon_url: str = data["icon_url"]
44
- self.id: int = data["id"]
45
- self.name: str = data["name"]
46
- self.grouping: str = data["grouping"]
47
- self.ordering: int = data["ordering"]
48
- self.slug: str = data["slug"]
49
- self.description: str = data["description"]
43
+ self.icon_url: str = get_required(data, "icon_url")
44
+ self.id: int = get_required(data, "id")
45
+ self.name: str = get_required(data, "name")
46
+ self.grouping: str = get_required(data, "grouping")
47
+ self.ordering: int = get_required(data, "ordering")
48
+ self.slug: str = get_required(data, "slug")
49
+ self.description: str = get_required(data, "description")
50
50
  self.mode: GameModeStr = get_optional(data, "mode", GameModeStr)
51
- self.instructions: Optional[str] = data["instructions"]
51
+ self.instructions: Optional[str] = get_required(data, "instructions")
52
52
 
53
53
  def __repr__(self):
54
54
  return prettify(self, "name", "description")
@@ -2,7 +2,7 @@ from dateutil import parser
2
2
  from typing import Dict, Optional, List, Union, TYPE_CHECKING
3
3
 
4
4
  from ..enums import RankStatus, GameModeStr, GameModeInt
5
- from ..util import prettify, get_optional, get_optional_list
5
+ from ..util import prettify, get_optional, get_optional_list, get_required
6
6
  from .user import UserCompact
7
7
  from .current_user_attributes import BeatmapsetPermissions
8
8
 
@@ -138,25 +138,25 @@ class BeatmapsetCompact:
138
138
  from .discussion import BeatmapsetDiscussion
139
139
  from .beatmapset_event import BeatmapsetEvent
140
140
 
141
- self.artist: str = data["artist"]
142
- self.artist_unicode: str = data["artist_unicode"]
143
- self.covers: Covers = Covers(data["covers"])
144
- self.creator: str = data["creator"]
145
- self.favourite_count: int = data["favourite_count"]
141
+ self.artist: str = get_required(data, "artist")
142
+ self.artist_unicode: str = get_required(data, "artist_unicode")
143
+ self.covers: Covers = Covers(get_required(data, "covers"))
144
+ self.creator: str = get_required(data, "creator")
145
+ self.favourite_count: int = get_required(data, "favourite_count")
146
146
  self.hype: Optional[BeatmapsetRequirement] = get_optional(data, "hype", BeatmapsetRequirement)
147
- self.id: int = data["id"]
148
- self.nsfw: bool = data["nsfw"]
149
- self.offset: int = data["offset"]
150
- self.play_count: int = data["play_count"]
151
- self.preview_url: str = data["preview_url"]
152
- self.source: str = data["source"]
153
- self.spotlight: bool = data["spotlight"]
154
- self.status: RankStatus = RankStatus[data["status"].upper()]
155
- self.title: str = data["title"]
156
- self.title_unicode: str = data["title_unicode"]
157
- self.track_id: Optional[int] = data["track_id"]
158
- self.user_id: int = data["user_id"]
159
- self.video: bool = data["video"]
147
+ self.id: int = get_required(data, "id")
148
+ self.nsfw: bool = get_required(data, "nsfw")
149
+ self.offset: int = get_required(data, "offset")
150
+ self.play_count: int = get_required(data, "play_count")
151
+ self.preview_url: str = get_required(data, "preview_url")
152
+ self.source: str = get_required(data, "source")
153
+ self.spotlight: bool = get_required(data, "spotlight")
154
+ self.status: RankStatus = RankStatus[get_required(data, "status").upper()]
155
+ self.title: str = get_required(data, "title")
156
+ self.title_unicode: str = get_required(data, "title_unicode")
157
+ self.track_id: Optional[int] = get_required(data, "track_id")
158
+ self.user_id: int = get_required(data, "user_id")
159
+ self.video: bool = get_required(data, "video")
160
160
 
161
161
  self.background_url: str = f"https://assets.ppy.sh/beatmaps/{self.id}/covers/raw.jpg"
162
162
 
@@ -251,22 +251,22 @@ class Beatmapset(BeatmapsetCompact):
251
251
  def __init__(self, data):
252
252
  super().__init__(data)
253
253
 
254
- self.availability: BeatmapsetAvailability = BeatmapsetAvailability(data["availability"])
254
+ self.availability: BeatmapsetAvailability = BeatmapsetAvailability(get_required(data, "availability"))
255
255
  self.beatmaps: Optional[List[Beatmap]] = get_optional_list(data, "beatmaps", Beatmap)
256
- self.bpm: float = data["bpm"]
257
- self.can_be_hyped: bool = data["can_be_hyped"]
256
+ self.bpm: float = get_required(data, "bpm")
257
+ self.can_be_hyped: bool = get_required(data, "can_be_hyped")
258
258
  self.deleted_at: Optional[datetime] = get_optional(data, "deleted_at", parser.parse)
259
259
  self.discussion_enabled: bool = True # Deprecated, all beatmapset discussions are enabled
260
- self.discussion_locked: bool = data["discussion_locked"]
261
- self.is_scoreable: bool = data["is_scoreable"]
260
+ self.discussion_locked: bool = get_required(data, "discussion_locked")
261
+ self.is_scoreable: bool = get_required(data, "is_scoreable")
262
262
  self.last_updated: Optional[datetime] = get_optional(data, "last_updated", parser.parse)
263
- self.legacy_thread_url: Optional[str] = data["legacy_thread_url"]
264
- self.nominations_summary: BeatmapsetRequirement = BeatmapsetRequirement(data["nominations_summary"])
265
- self.ranked: RankStatus = RankStatus(data["ranked"])
263
+ self.legacy_thread_url: Optional[str] = get_required(data, "legacy_thread_url")
264
+ self.nominations_summary: BeatmapsetRequirement = BeatmapsetRequirement(get_required(data, "nominations_summary"))
265
+ self.ranked: RankStatus = RankStatus(get_required(data, "ranked"))
266
266
  self.ranked_date: Optional[datetime] = get_optional(data, "ranked_date", parser.parse)
267
- self.storyboard: bool = data["storyboard"]
267
+ self.storyboard: bool = get_required(data, "storyboard")
268
268
  self.submitted_date: Optional[datetime] = get_optional(data, "submitted_date", parser.parse)
269
- self.tags: str = data["tags"]
269
+ self.tags: str = get_required(data, "tags")
270
270
 
271
271
 
272
272
  class BeatmapCompact:
@@ -319,14 +319,14 @@ class BeatmapCompact:
319
319
  )
320
320
 
321
321
  def __init__(self, data):
322
- self.beatmapset_id: int = data["beatmapset_id"]
323
- self.difficulty_rating: float = data["difficulty_rating"]
324
- self.id: int = data["id"]
325
- self.mode: GameModeStr = GameModeStr(data["mode"])
326
- self.status: RankStatus = RankStatus[data["status"].upper()]
327
- self.total_length: int = data["total_length"]
328
- self.user_id: int = data["user_id"]
329
- self.version: str = data["version"]
322
+ self.beatmapset_id: int = get_required(data, "beatmapset_id")
323
+ self.difficulty_rating: float = get_required(data, "difficulty_rating")
324
+ self.id: int = get_required(data, "id")
325
+ self.mode: GameModeStr = GameModeStr(get_required(data, "mode"))
326
+ self.status: RankStatus = RankStatus[get_required(data, "status").upper()]
327
+ self.total_length: int = get_required(data, "total_length")
328
+ self.user_id: int = get_required(data, "user_id")
329
+ self.version: str = get_required(data, "version")
330
330
 
331
331
  self.beatmapset: Optional[BeatmapsetCompact] = get_optional(data, "beatmapset", BeatmapsetCompact)
332
332
  self.checksum: Optional[str] = data.get("checksum")
@@ -412,25 +412,25 @@ class Beatmap(BeatmapCompact):
412
412
  def __init__(self, data):
413
413
  super().__init__(data)
414
414
 
415
- self.accuracy: float = data["accuracy"]
416
- self.ar: float = data["ar"]
415
+ self.accuracy: float = get_required(data, "accuracy")
416
+ self.ar: float = get_required(data, "ar")
417
417
  self.beatmapset: Optional[Beatmapset] = get_optional(data, "beatmapset", Beatmapset)
418
- self.bpm: float = data["bpm"]
419
- self.convert: Optional[bool] = data["convert"]
420
- self.count_circles: int = data["count_circles"]
421
- self.count_sliders: int = data["count_sliders"]
422
- self.count_spinners: int = data["count_spinners"]
423
- self.cs: float = data["cs"]
418
+ self.bpm: float = get_required(data, "bpm")
419
+ self.convert: Optional[bool] = get_required(data, "convert")
420
+ self.count_circles: int = get_required(data, "count_circles")
421
+ self.count_sliders: int = get_required(data, "count_sliders")
422
+ self.count_spinners: int = get_required(data, "count_spinners")
423
+ self.cs: float = get_required(data, "cs")
424
424
  self.deleted_at: Optional[datetime] = get_optional(data, "deleted_at", parser.parse)
425
- self.drain: float = data["drain"]
426
- self.hit_length: int = data["hit_length"]
427
- self.is_scoreable: bool = data["is_scoreable"]
428
- self.last_updated: datetime = parser.parse(data["last_updated"])
429
- self.mode_int: GameModeInt = GameModeInt(data["mode_int"])
430
- self.passcount: int = data["passcount"]
431
- self.playcount: int = data["playcount"]
432
- self.ranked: RankStatus = RankStatus(data["ranked"])
433
- self.url: str = data["url"]
425
+ self.drain: float = get_required(data, "drain")
426
+ self.hit_length: int = get_required(data, "hit_length")
427
+ self.is_scoreable: bool = get_required(data, "is_scoreable")
428
+ self.last_updated: datetime = parser.parse(get_required(data, "last_updated"))
429
+ self.mode_int: GameModeInt = GameModeInt(get_required(data, "mode_int"))
430
+ self.passcount: int = get_required(data, "passcount")
431
+ self.playcount: int = get_required(data, "playcount")
432
+ self.ranked: RankStatus = RankStatus(get_required(data, "ranked"))
433
+ self.url: str = get_required(data, "url")
434
434
 
435
435
 
436
436
  class MetadataAttribute:
@@ -447,8 +447,8 @@ class MetadataAttribute:
447
447
  __slots__ = ("id", "name")
448
448
 
449
449
  def __init__(self, data):
450
- self.id: Optional[int] = data["id"]
451
- self.name: str = data["name"]
450
+ self.id: Optional[int] = get_required(data, "id")
451
+ self.name: str = get_required(data, "name")
452
452
 
453
453
 
454
454
  class OsuBeatmapDifficultyAttributes:
@@ -484,13 +484,13 @@ class OsuBeatmapDifficultyAttributes:
484
484
  )
485
485
 
486
486
  def __init__(self, data):
487
- self.aim_difficulty: float = data["aim_difficulty"]
488
- self.approach_rate: float = data["approach_rate"]
489
- self.flashlight_difficulty: float = data["flashlight_difficulty"]
490
- self.overall_difficulty: float = data["overall_difficulty"]
491
- self.slider_factor: float = data["slider_factor"]
492
- self.speed_difficulty: float = data["speed_difficulty"]
493
- self.speed_note_count: float = data["speed_note_count"]
487
+ self.aim_difficulty: float = get_required(data, "aim_difficulty")
488
+ self.approach_rate: float = get_required(data, "approach_rate")
489
+ self.flashlight_difficulty: float = get_required(data, "flashlight_difficulty")
490
+ self.overall_difficulty: float = get_required(data, "overall_difficulty")
491
+ self.slider_factor: float = get_required(data, "slider_factor")
492
+ self.speed_difficulty: float = get_required(data, "speed_difficulty")
493
+ self.speed_note_count: float = get_required(data, "speed_note_count")
494
494
 
495
495
  def __repr__(self):
496
496
  return prettify(self, "aim_difficulty", "speed_difficulty")
@@ -523,11 +523,11 @@ class TaikoBeatmapDifficultyAttributes:
523
523
  )
524
524
 
525
525
  def __init__(self, data):
526
- self.stamina_difficulty: float = data["stamina_difficulty"]
527
- self.rhythm_difficulty: float = data["rhythm_difficulty"]
528
- self.colour_difficulty: float = data["colour_difficulty"]
529
- self.great_hit_window: float = data["great_hit_window"]
530
- self.peak_difficulty: float = data["peak_difficulty"]
526
+ self.stamina_difficulty: float = get_required(data, "stamina_difficulty")
527
+ self.rhythm_difficulty: float = get_required(data, "rhythm_difficulty")
528
+ self.colour_difficulty: float = get_required(data, "colour_difficulty")
529
+ self.great_hit_window: float = get_required(data, "great_hit_window")
530
+ self.peak_difficulty: float = get_required(data, "peak_difficulty")
531
531
 
532
532
  def __repr__(self):
533
533
  return prettify(self, "stamina_difficulty")
@@ -546,7 +546,7 @@ class FruitsBeatmapDifficultyAttributes:
546
546
  __slots__ = "approach_rate"
547
547
 
548
548
  def __init__(self, data):
549
- self.approach_rate: float = data["approach_rate"]
549
+ self.approach_rate: float = get_required(data, "approach_rate")
550
550
 
551
551
  def __repr__(self):
552
552
  return prettify(self, "approach_rate")
@@ -567,8 +567,8 @@ class ManiaBeatmapDifficultyAttributes:
567
567
  __slots__ = ("great_hit_window", "score_multiplier")
568
568
 
569
569
  def __init__(self, data):
570
- self.great_hit_window: float = data["great_hit_window"]
571
- self.score_multiplier: float = data["score_multiplier"]
570
+ self.great_hit_window: float = get_required(data, "great_hit_window")
571
+ self.score_multiplier: float = get_required(data, "score_multiplier")
572
572
 
573
573
  def __repr__(self):
574
574
  return prettify(self, "great_hit_window")
@@ -607,9 +607,9 @@ class BeatmapDifficultyAttributes:
607
607
  ]
608
608
 
609
609
  def __init__(self, data):
610
- data = data["attributes"]
611
- self.max_combo: int = data["max_combo"]
612
- self.star_rating: float = data["star_rating"]
610
+ data = get_required(data, "attributes")
611
+ self.max_combo: int = get_required(data, "max_combo")
612
+ self.star_rating: float = get_required(data, "star_rating")
613
613
  if "aim_difficulty" in data:
614
614
  self.type = GameModeStr.STANDARD
615
615
  self.mode_attributes = OsuBeatmapDifficultyAttributes(data)
@@ -687,13 +687,13 @@ class Covers:
687
687
  )
688
688
 
689
689
  def __init__(self, data):
690
- self.cover: str = data["cover"]
690
+ self.cover: str = get_required(data, "cover")
691
691
  self.cover_2x: str = data["cover@2x"]
692
- self.card: str = data["card"]
692
+ self.card: str = get_required(data, "card")
693
693
  self.card_2x: str = data["card@2x"]
694
- self.list: str = data["list"]
694
+ self.list: str = get_required(data, "list")
695
695
  self.list_2x: str = data["list@2x"]
696
- self.slimcover: str = data["slimcover"]
696
+ self.slimcover: str = get_required(data, "slimcover")
697
697
  self.slimcover_2x: str = data["slimcover@2x"]
698
698
 
699
699
  def __repr__(self):
@@ -718,10 +718,10 @@ class BeatmapPlaycount:
718
718
  __slots__ = ("beatmap_id", "beatmap", "beatmapset", "count")
719
719
 
720
720
  def __init__(self, data):
721
- self.beatmap_id: int = data["beatmap_id"]
721
+ self.beatmap_id: int = get_required(data, "beatmap_id")
722
722
  self.beatmap: Optional[BeatmapCompact] = get_optional(data, "beatmap", BeatmapCompact)
723
723
  self.beatmapset: Optional[BeatmapsetCompact] = get_optional(data, "beatmapset", BeatmapsetCompact)
724
- self.count: int = data["count"]
724
+ self.count: int = get_required(data, "count")
725
725
 
726
726
  def __repr__(self):
727
727
  return prettify(self, "beatmap_id", "count")
@@ -746,7 +746,7 @@ class BeatmapsetRequirement:
746
746
  __slots__ = ("current", "required", "eligible_main_rulesets", "required_meta")
747
747
 
748
748
  def __init__(self, data):
749
- self.current: int = data["current"]
749
+ self.current: int = get_required(data, "current")
750
750
  self.required: Optional[int] = data.get("required")
751
751
 
752
752
  self.eligible_main_rulesets: Optional[List[GameModeStr]] = get_optional_list(
@@ -774,8 +774,8 @@ class BeatmapsetRequiredNominations:
774
774
  ___slots__ = ("main_ruleset", "non_main_ruleset")
775
775
 
776
776
  def __init__(self, data):
777
- self.main_ruleset = data["main_ruleset"]
778
- self.non_main_ruleset = data["non_main_ruleset"]
777
+ self.main_ruleset = get_required(data, "main_ruleset")
778
+ self.non_main_ruleset = get_required(data, "non_main_ruleset")
779
779
 
780
780
 
781
781
  class BeatmapsetAvailability:
@@ -792,7 +792,7 @@ class BeatmapsetAvailability:
792
792
  __slots__ = ("download_disabled", "more_information")
793
793
 
794
794
  def __init__(self, data):
795
- self.download_disabled: bool = data["download_disabled"]
795
+ self.download_disabled: bool = get_required(data, "download_disabled")
796
796
  self.more_information: Optional[str] = data.get("more_information")
797
797
 
798
798
  def __repr__(self):
@@ -835,7 +835,7 @@ class BaseNominations:
835
835
  self.nomination_reset: Optional[BeatmapsetEvent] = get_optional(data, "nomination_reset", BeatmapsetEvent)
836
836
  self.ranking_eta: Optional[str] = data.get("ranking_eta")
837
837
  self.ranking_queue_position: Optional[int] = data.get("ranking_queue_position")
838
- self.required_hype: int = data["required_hype"]
838
+ self.required_hype: int = get_required(data, "required_hype")
839
839
 
840
840
  def __repr__(self):
841
841
  return prettify(self, "nominated")
@@ -860,8 +860,8 @@ class LegacyNominations(BaseNominations):
860
860
 
861
861
  def __init__(self, data):
862
862
  super().__init__(data)
863
- self.current: int = data["current"]
864
- self.required: int = data["required"]
863
+ self.current: int = get_required(data, "current")
864
+ self.required: int = get_required(data, "required")
865
865
 
866
866
  def __repr__(self):
867
867
  return prettify("current", "required")
@@ -887,11 +887,11 @@ class Nominations(BaseNominations):
887
887
  def __init__(self, data):
888
888
  super().__init__(data)
889
889
  self.current: Dict[GameModeStr, int] = dict(
890
- zip(map(GameModeStr, (current := data["current"]).keys()), current.values())
890
+ zip(map(GameModeStr, (current := get_required(data, "current")).keys()), current.values())
891
891
  )
892
892
  self.required: Dict[GameModeStr, int] = dict(
893
893
  zip(
894
- map(GameModeStr, (required := data["required"]).keys()),
894
+ map(GameModeStr, (required := get_required(data, "required")).keys()),
895
895
  required.values(),
896
896
  )
897
897
  )
@@ -916,10 +916,10 @@ class CurrentNomination:
916
916
  """
917
917
 
918
918
  def __init__(self, data):
919
- self.beatmapset_id: int = data["beatmapset_id"]
919
+ self.beatmapset_id: int = get_required(data, "beatmapset_id")
920
920
  self.rulesets: Optional[List[GameModeStr]] = get_optional_list(data, "rulesets", GameModeStr)
921
- self.reset: bool = data["reset"]
922
- self.user_id: int = data["user_id"]
921
+ self.reset: bool = get_required(data, "reset")
922
+ self.user_id: int = get_required(data, "user_id")
923
923
 
924
924
  def __repr__(self):
925
925
  return prettify(self, "beatmapset_id", "user_id")
@@ -2,7 +2,7 @@ from dateutil import parser
2
2
  from typing import Optional, List, TYPE_CHECKING
3
3
 
4
4
  from ..enums import GameModeStr, BeatmapsetEventType
5
- from ..util import prettify
5
+ from ..util import prettify, get_required
6
6
  from .discussion import BeatmapsetDiscussion
7
7
  from .beatmap import BeatmapsetCompact
8
8
 
@@ -78,18 +78,18 @@ class BeatmapsetEvent:
78
78
  )
79
79
 
80
80
  def __init__(self, data):
81
- self.id: int = data["id"]
82
- self.type: BeatmapsetEventType = BeatmapsetEventType(data["type"])
83
- self.comment: Optional[BeatmapsetEventComment] = data["comment"]
81
+ self.id: int = get_required(data, "id")
82
+ self.type: BeatmapsetEventType = BeatmapsetEventType(get_required(data, "type"))
83
+ self.comment: Optional[BeatmapsetEventComment] = get_required(data, "comment")
84
84
  if self.comment is not None:
85
85
  self.comment = BeatmapsetEventComment(self.comment, self.type) # type: ignore
86
- self.created_at: datetime = parser.parse(data["created_at"])
86
+ self.created_at: datetime = parser.parse(get_required(data, "created_at"))
87
87
  self.user_id: Optional[int] = data.get("user_id")
88
88
  self.beatmapset: Optional[BeatmapsetCompact] = (
89
- BeatmapsetCompact(data["beatmapset"]) if data.get("beatmapset") is not None else None
89
+ BeatmapsetCompact(get_required(data, "beatmapset")) if data.get("beatmapset") is not None else None
90
90
  )
91
91
  self.discussion: Optional[BeatmapsetDiscussion] = (
92
- BeatmapsetDiscussion(data["discussion"]) if data.get("discussion") is not None else None
92
+ BeatmapsetDiscussion(get_required(data, "discussion")) if data.get("discussion") is not None else None
93
93
  )
94
94
 
95
95
  def __repr__(self):
@@ -106,7 +106,7 @@ class BeatmapsetEventNominate:
106
106
  __slots__ = ("modes",)
107
107
 
108
108
  def __init__(self, data):
109
- self.modes: List[GameModeStr] = list(map(GameModeStr, data["modes"]))
109
+ self.modes: List[GameModeStr] = list(map(GameModeStr, get_required(data, "modes")))
110
110
 
111
111
  def __repr__(self):
112
112
  return prettify(self, "modes")
@@ -122,7 +122,7 @@ class BeatmapsetEventRemoveFromLoved:
122
122
  __slots__ = ("reason",)
123
123
 
124
124
  def __init__(self, data):
125
- self.reason: str = data["reason"]
125
+ self.reason: str = get_required(data, "reason")
126
126
 
127
127
  def __repr__(self):
128
128
  return prettify(self, "reason")
@@ -138,7 +138,7 @@ class BeatmapsetEventDisqualify:
138
138
  __slots__ = ("nominator_ids",)
139
139
 
140
140
  def __init__(self, data):
141
- self.nominator_ids: List[int] = data["nominator_ids"]
141
+ self.nominator_ids: List[int] = get_required(data, "nominator_ids")
142
142
 
143
143
  def __repr__(self):
144
144
  return prettify(self, "nominator_ids")
@@ -156,8 +156,8 @@ class BeatmapsetEventVote:
156
156
  __slots__ = ("user_id", "score")
157
157
 
158
158
  def __init__(self, data):
159
- self.user_id: int = data["user_id"]
160
- self.score: int = data["score"]
159
+ self.user_id: int = get_required(data, "user_id")
160
+ self.score: int = get_required(data, "score")
161
161
 
162
162
  def __repr__(self):
163
163
  return prettify(self, "user_id", "score")
@@ -176,10 +176,10 @@ class BeatmapsetEventKudosuChange:
176
176
 
177
177
  def __init__(self, data):
178
178
  self.new_votes: Optional[BeatmapsetEventVote] = (
179
- BeatmapsetEventVote(data["new_votes"]) if data.get("new_votes") is not None else None
179
+ BeatmapsetEventVote(get_required(data, "new_votes")) if data.get("new_votes") is not None else None
180
180
  )
181
181
  self.votes: Optional[List[BeatmapsetEventVote]] = (
182
- list(map(BeatmapsetEventVote, data["votes"])) if data.get("votes") is not None else None
182
+ list(map(BeatmapsetEventVote, get_required(data, "votes"))) if data.get("votes") is not None else None
183
183
  )
184
184
 
185
185
  def __repr__(self):
@@ -205,7 +205,7 @@ class BeatmapsetEventKudosuRecalculate:
205
205
 
206
206
  def __init__(self, data):
207
207
  self.new_votes: Optional[BeatmapsetEventVote] = (
208
- BeatmapsetEventVote(data["new_votes"]) if data.get("new_votes") is not None else None
208
+ BeatmapsetEventVote(get_required(data, "new_votes")) if data.get("new_votes") is not None else None
209
209
  )
210
210
 
211
211
  def __repr__(self):
@@ -222,7 +222,7 @@ class BeatmapsetEventDiscussionLock:
222
222
  __slots__ = ("reason",)
223
223
 
224
224
  def __init__(self, data):
225
- self.reason: str = data["reason"]
225
+ self.reason: str = get_required(data, "reason")
226
226
 
227
227
  def __repr__(self):
228
228
  return prettify(self, "reason")
@@ -238,7 +238,7 @@ class BeatmapsetEventNominationReset:
238
238
  __slots__ = ("nominator_ids",)
239
239
 
240
240
  def __init__(self, data):
241
- self.nominator_ids: List[int] = data["nominator_ids"]
241
+ self.nominator_ids: List[int] = get_required(data, "nominator_ids")
242
242
 
243
243
  def __repr__(self):
244
244
  return prettify(self, "nominator_ids")
@@ -256,8 +256,8 @@ class BeatmapsetEventNominationResetReceived:
256
256
  __slots__ = ("source_user_id", "source_user_username")
257
257
 
258
258
  def __init__(self, data):
259
- self.source_user_id: int = data["source_user_id"]
260
- self.source_user_username: str = data["source_user_username"]
259
+ self.source_user_id: int = get_required(data, "source_user_id")
260
+ self.source_user_username: str = get_required(data, "source_user_username")
261
261
 
262
262
  def __repr__(self):
263
263
  return prettify(self, "source_user_id", "source_user_username")
@@ -275,8 +275,8 @@ class BeatmapsetEventEdit:
275
275
  __slots__ = ("old", "new")
276
276
 
277
277
  def __init__(self, data):
278
- self.old: str = data["old"]
279
- self.new: str = data["new"]
278
+ self.old: str = get_required(data, "old")
279
+ self.new: str = get_required(data, "new")
280
280
 
281
281
  def __repr__(self):
282
282
  return prettify(self, "old", "new")
@@ -314,10 +314,10 @@ class BeatmapsetEventBeatmapOwnerChange:
314
314
  __slots__ = ("beatmap_id", "beatmap_version", "new_user_id", "new_user_username")
315
315
 
316
316
  def __init__(self, data):
317
- self.beatmap_id: int = data["beatmap_id"]
318
- self.beatmap_version: str = data["beatmap_version"]
319
- self.new_user_id: int = data["new_user_id"]
320
- self.new_user_username: str = data["new_user_username"]
317
+ self.beatmap_id: int = get_required(data, "beatmap_id")
318
+ self.beatmap_version: str = get_required(data, "beatmap_version")
319
+ self.new_user_id: int = get_required(data, "new_user_id")
320
+ self.new_user_username: str = get_required(data, "new_user_username")
321
321
 
322
322
  def __repr__(self):
323
323
  return prettify(self, "beatmap_id", "new_user_username")