osu.py 2.2.2__tar.gz → 2.3.0__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.2/osu.py.egg-info → osu_py-2.3.0}/PKG-INFO +1 -1
  2. {osu_py-2.2.2 → osu_py-2.3.0}/osu/__init__.py +1 -1
  3. {osu_py-2.2.2 → osu_py-2.3.0}/osu/asyncio/client.py +21 -4
  4. {osu_py-2.2.2 → osu_py-2.3.0}/osu/client.py +21 -7
  5. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/beatmap.py +3 -1
  6. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/comment.py +3 -1
  7. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/match.py +3 -1
  8. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/score.py +12 -5
  9. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/user.py +10 -3
  10. {osu_py-2.2.2 → osu_py-2.3.0}/osu/path.py +4 -0
  11. {osu_py-2.2.2 → osu_py-2.3.0}/osu/util.py +3 -0
  12. {osu_py-2.2.2 → osu_py-2.3.0/osu.py.egg-info}/PKG-INFO +1 -1
  13. {osu_py-2.2.2 → osu_py-2.3.0}/LICENSE +0 -0
  14. {osu_py-2.2.2 → osu_py-2.3.0}/MANIFEST.in +0 -0
  15. {osu_py-2.2.2 → osu_py-2.3.0}/README.rst +0 -0
  16. {osu_py-2.2.2 → osu_py-2.3.0}/osu/asyncio/__init__.py +0 -0
  17. {osu_py-2.2.2 → osu_py-2.3.0}/osu/asyncio/http.py +0 -0
  18. {osu_py-2.2.2 → osu_py-2.3.0}/osu/auth.py +0 -0
  19. {osu_py-2.2.2 → osu_py-2.3.0}/osu/constants.py +0 -0
  20. {osu_py-2.2.2 → osu_py-2.3.0}/osu/enums.py +0 -0
  21. {osu_py-2.2.2 → osu_py-2.3.0}/osu/exceptions.py +0 -0
  22. {osu_py-2.2.2 → osu_py-2.3.0}/osu/http.py +0 -0
  23. {osu_py-2.2.2 → osu_py-2.3.0}/osu/notification.py +0 -0
  24. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/__init__.py +0 -0
  25. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/achievement.py +0 -0
  26. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/beatmapset_event.py +0 -0
  27. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/build.py +0 -0
  28. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/chat.py +0 -0
  29. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/current_user_attributes.py +0 -0
  30. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/discussion.py +0 -0
  31. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/event.py +0 -0
  32. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/forum.py +0 -0
  33. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/group.py +0 -0
  34. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/kudosu.py +0 -0
  35. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/multiplayer.py +0 -0
  36. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/news.py +0 -0
  37. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/notification.py +0 -0
  38. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/ranking.py +0 -0
  39. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/scope.py +0 -0
  40. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/seasonal_background.py +0 -0
  41. {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/wiki.py +0 -0
  42. {osu_py-2.2.2 → osu_py-2.3.0}/osu/results.py +0 -0
  43. {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/SOURCES.txt +0 -0
  44. {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/dependency_links.txt +0 -0
  45. {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/requires.txt +0 -0
  46. {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/top_level.txt +0 -0
  47. {osu_py-2.2.2 → osu_py-2.3.0}/pyproject.toml +0 -0
  48. {osu_py-2.2.2 → osu_py-2.3.0}/requirements.txt +0 -0
  49. {osu_py-2.2.2 → osu_py-2.3.0}/setup.cfg +0 -0
  50. {osu_py-2.2.2 → osu_py-2.3.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osu.py
3
- Version: 2.2.2
3
+ Version: 2.3.0
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.2"
17
+ __version__ = "2.3.0"
@@ -103,7 +103,7 @@ class AsynchronousClient:
103
103
  code: Optional[str] = None,
104
104
  request_wait_time: float = 1.0,
105
105
  limit_per_minute: int = 60,
106
- lazily_authenticate: bool = True
106
+ lazily_authenticate: bool = True,
107
107
  ) -> Union["AsynchronousClient", Awaitable]:
108
108
  """
109
109
  Creates client from client id, client secret, redirect uri, and scope.
@@ -147,6 +147,7 @@ class AsynchronousClient:
147
147
  """
148
148
  auth = AsynchronousAuthHandler(client_id, client_secret, redirect_url, scope)
149
149
  if not lazily_authenticate:
150
+
150
151
  async def create():
151
152
  await auth.get_auth_token(code)
152
153
  return cls(auth, request_wait_time, limit_per_minute)
@@ -268,9 +269,9 @@ class AsynchronousClient:
268
269
  return
269
270
  return list(
270
271
  map(
271
- lambda mod: (Mod[mod.name].value if not isinstance(mod, Mod) else mod.value)
272
- if type(mod) != str
273
- else mod,
272
+ lambda mod: (
273
+ (Mod[mod.name].value if not isinstance(mod, Mod) else mod.value) if type(mod) != str else mod
274
+ ),
274
275
  mods,
275
276
  )
276
277
  )
@@ -1492,6 +1493,22 @@ class AsynchronousClient:
1492
1493
  res = await self.http.make_request(Path.get_users(), **{"ids[]": ids})
1493
1494
  return list(map(UserCompact, res["users"]))
1494
1495
 
1496
+ async def lookup_users(self, users: list[int, str]):
1497
+ """
1498
+ Lookup users by a mix of user ids and usernames.
1499
+ Can lookup maximum 50 at a time.
1500
+
1501
+ ids: Sequence[Union[:class:`int`, :class:`str`]]
1502
+ Can be a list of user ids and usernames.
1503
+ Usernames should be prefixed with "@" to make sure they're interpreted as usernames by the api.
1504
+
1505
+ **Returns**
1506
+
1507
+ Sequence[:class:`UserCompact`]
1508
+ """
1509
+ res = await self.http.make_request(Path.lookup_users(), **{"ids[]": users})
1510
+ return list(map(UserCompact, res["users"]))
1511
+
1495
1512
  async def get_wiki_page(self, locale: str, path: str) -> WikiPage:
1496
1513
  """
1497
1514
  The wiki article or image data.
@@ -76,7 +76,7 @@ class Client:
76
76
  code: Optional[str] = None,
77
77
  request_wait_time: float = 1.0,
78
78
  limit_per_minute: int = 60,
79
- lazily_authenticate: bool = True
79
+ lazily_authenticate: bool = True,
80
80
  ) -> "Client":
81
81
  """
82
82
  Returns a :class:`Client` object from client id, client secret, redirect uri, and scope.
@@ -243,9 +243,9 @@ class Client:
243
243
  return
244
244
  return list(
245
245
  map(
246
- lambda mod: (Mod[mod.name].value if not isinstance(mod, Mod) else mod.value)
247
- if type(mod) != str
248
- else mod,
246
+ lambda mod: (
247
+ (Mod[mod.name].value if not isinstance(mod, Mod) else mod.value) if type(mod) != str else mod
248
+ ),
249
249
  mods,
250
250
  )
251
251
  )
@@ -1450,6 +1450,7 @@ class Client:
1450
1450
  def get_users(self, ids: Sequence[int]) -> List[UserCompact]:
1451
1451
  """
1452
1452
  Returns list of users.
1453
+ Can get maximum 50 at a time.
1453
1454
 
1454
1455
  Requires OAuth and scope public
1455
1456
 
@@ -1465,9 +1466,22 @@ class Client:
1465
1466
  Includes attributes: country, cover, groups, statistics_rulesets.
1466
1467
  """
1467
1468
  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)
1469
+ return list(map(UserCompact, res["users"]))
1470
+
1471
+ def lookup_users(self, users: list[int, str]):
1472
+ """
1473
+ Lookup users by a mix of user ids and usernames.
1474
+ Can lookup maximum 50 at a time.
1475
+
1476
+ ids: Sequence[Union[:class:`int`, :class:`str`]]
1477
+ Can be a list of user ids and usernames.
1478
+ Usernames should be prefixed with "@" to make sure they're interpreted as usernames by the api.
1479
+
1480
+ **Returns**
1481
+
1482
+ Sequence[:class:`UserCompact`]
1483
+ """
1484
+ res = self.http.make_request(Path.lookup_users(), **{"ids[]": users})
1471
1485
  return list(map(UserCompact, res["users"]))
1472
1486
 
1473
1487
  def get_wiki_page(self, locale: str, path: str) -> WikiPage:
@@ -261,7 +261,9 @@ class Beatmapset(BeatmapsetCompact):
261
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
263
  self.legacy_thread_url: Optional[str] = get_required(data, "legacy_thread_url")
264
- self.nominations_summary: BeatmapsetRequirement = BeatmapsetRequirement(get_required(data, "nominations_summary"))
264
+ self.nominations_summary: BeatmapsetRequirement = BeatmapsetRequirement(
265
+ get_required(data, "nominations_summary")
266
+ )
265
267
  self.ranked: RankStatus = RankStatus(get_required(data, "ranked"))
266
268
  self.ranked_date: Optional[datetime] = get_optional(data, "ranked_date", parser.parse)
267
269
  self.storyboard: bool = get_required(data, "storyboard")
@@ -176,7 +176,9 @@ class CommentBundle:
176
176
  )
177
177
 
178
178
  def __init__(self, data):
179
- self.commentable_meta: List[CommentableMeta] = list(map(CommentableMeta, get_required(data, "commentable_meta")))
179
+ self.commentable_meta: List[CommentableMeta] = list(
180
+ map(CommentableMeta, get_required(data, "commentable_meta"))
181
+ )
180
182
  self.comments: List[Comment] = list(map(Comment, get_required(data, "comments")))
181
183
  self.has_more: bool = get_required(data, "has_more")
182
184
  self.has_more_id: int = get_required(data, "has_more_id")
@@ -109,7 +109,9 @@ class MatchEvent:
109
109
  self.timestamp: datetime = parser.parse(get_required(data, "timestamp"))
110
110
  self.user_id: int = get_required(data, "user_id")
111
111
  self.type: MatchEventType = MatchEventType(get_required(data, "detail")["type"])
112
- self.text: Optional[str] = get_required(data, "detail")["text"] if "text" in get_required(data, "detail") else None
112
+ self.text: Optional[str] = (
113
+ get_required(data, "detail")["text"] if "text" in get_required(data, "detail") else None
114
+ )
113
115
  self.game: Optional[MatchGame] = get_optional(data, "game", MatchGame)
114
116
 
115
117
  def __repr__(self):
@@ -1,7 +1,7 @@
1
1
  from dateutil import parser
2
2
  from typing import Optional, List, TYPE_CHECKING, Union, Dict
3
3
 
4
- from .beatmap import BeatmapCompact, BeatmapsetCompact
4
+ from .beatmap import BeatmapCompact, BeatmapsetCompact, Beatmap
5
5
  from .user import UserCompact
6
6
  from .current_user_attributes import ScoreUserAttributes
7
7
  from ..enums import GameModeStr, GameModeInt, Mods, Mod, ObjectType, ScoreRank
@@ -176,6 +176,10 @@ class SoloScore:
176
176
 
177
177
  beatmap_id: Optional[:class:`int`]
178
178
 
179
+ beatmap: Optional[:class:`Beatmap`]
180
+
181
+ beatmapset: Optional[:class:`BeatmapsetCompact`]
182
+
179
183
  ended_at: :class:`datetime.datetime`
180
184
 
181
185
  max_combo: :class:`int`
@@ -249,6 +253,8 @@ class SoloScore:
249
253
  "user",
250
254
  "current_user_attributes",
251
255
  "weight",
256
+ "beatmap",
257
+ "beatmapset",
252
258
  )
253
259
 
254
260
  def __init__(self, data):
@@ -280,6 +286,8 @@ class SoloScore:
280
286
  data, "current_user_attributes", ScoreUserAttributes
281
287
  )
282
288
  self.weight: Optional[PpWeight] = get_optional(data, "weight", PpWeight)
289
+ self.beatmap: Optional[Beatmap] = get_optional(data, "beatmap", Beatmap)
290
+ self.beatmapset: Optional[BeatmapsetCompact] = get_optional(data, "beatmapset", BeatmapsetCompact)
283
291
 
284
292
  def __repr__(self):
285
293
  return prettify(self, "beatmap_id", "statistics")
@@ -306,11 +314,10 @@ class PpWeight:
306
314
 
307
315
 
308
316
  def get_score_object(data) -> Union[SoloScore, LegacyScore]:
309
- # EAFP cuz type attribute is not reliable
310
- try:
317
+ if data["type"] == "solo_score":
311
318
  return SoloScore(data)
312
- except KeyError:
313
- return LegacyScore(data)
319
+
320
+ return LegacyScore(data)
314
321
 
315
322
 
316
323
  class ScoreStatistics:
@@ -338,6 +338,9 @@ class User(UserCompact):
338
338
  post_count: :class:`int`
339
339
  number of forum posts
340
340
 
341
+ profile_hue: Optional[:class:`int`]
342
+ Custom color hue in HSL degrees (0-359).
343
+
341
344
  profile_order: List[:class:`str`]
342
345
  Ordered list of sections in user profile page. Sections consist of:
343
346
  me, recent_activity, beatmaps, historical, kudosu, top_ranks, medals
@@ -365,11 +368,12 @@ class User(UserCompact):
365
368
  "playmode",
366
369
  "playstyle",
367
370
  "post_count",
371
+ "profile_hue",
368
372
  "profile_order",
369
373
  "title",
370
374
  "title_url",
371
375
  "twitter",
372
- "website"
376
+ "website",
373
377
  )
374
378
 
375
379
  def __init__(self, data):
@@ -387,6 +391,7 @@ class User(UserCompact):
387
391
  self.playmode: GameModeStr = GameModeStr(get_required(data, "playmode"))
388
392
  self.playstyle: List[str] = get_required(data, "playstyle")
389
393
  self.post_count: int = get_required(data, "post_count")
394
+ self.profile_hue: Optional[int] = get_required(data, "profile_hue")
390
395
  self.profile_order: List[str] = get_required(data, "profile_order")
391
396
  self.title: Optional[str] = get_required(data, "title")
392
397
  self.title_url: Optional[str] = get_required(data, "title_url")
@@ -753,7 +758,9 @@ class UserStatistics:
753
758
  self.country_rank: Optional[int] = data.get("country_rank")
754
759
  self.global_rank: Optional[int] = get_required(data, "global_rank")
755
760
  self.global_rank_exp: Optional[int] = data.get("global_rank_exp")
756
- self.grade_counts: NamedTuple = namedtuple("GradeCounts", ("ssh", "ss", "sh", "s", "a"))(**get_required(data, "grade_counts"))
761
+ self.grade_counts: NamedTuple = namedtuple("GradeCounts", ("ssh", "ss", "sh", "s", "a"))(
762
+ **get_required(data, "grade_counts")
763
+ )
757
764
  self.level: NamedTuple = namedtuple("Level", ("current", "progress"))(**get_required(data, "level"))
758
765
  self.hit_accuracy: float = get_required(data, "hit_accuracy")
759
766
  self.is_ranked: bool = get_required(data, "is_ranked")
@@ -1027,7 +1034,7 @@ class DailyChallengeUserStats:
1027
1034
  "top_50p_placements",
1028
1035
  "user_id",
1029
1036
  "weekly_streak_best",
1030
- "weekly_streak_current"
1037
+ "weekly_streak_current",
1031
1038
  )
1032
1039
 
1033
1040
  def __init__(self, data):
@@ -172,6 +172,10 @@ class Path:
172
172
  def get_users(cls):
173
173
  return cls("get", "users", "public")
174
174
 
175
+ @classmethod
176
+ def lookup_users(cls):
177
+ return cls("get", "users/lookup", "public")
178
+
175
179
  @classmethod
176
180
  def get_wiki_page(cls, locale, path):
177
181
  return cls("get", f"wiki/{locale}/{path}", None)
@@ -396,8 +396,11 @@ def get_optional_list(data: Dict[str, _V], key: str, obj: Callable[[_V], _T]) ->
396
396
  # if testing, raise an error for expected keys
397
397
  # otherwise return none to avoid key errors in prod
398
398
  if os.getenv("OSUPY_TEST") is None:
399
+
399
400
  def get_required(data: Dict[str, _V], key: str):
400
401
  return data.get(key)
402
+
401
403
  else:
404
+
402
405
  def get_required(data: Dict[str, _V], key: str):
403
406
  return data[key]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osu.py
3
- Version: 2.2.2
3
+ Version: 2.3.0
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes