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.
- {osu_py-2.2.2/osu.py.egg-info → osu_py-2.3.0}/PKG-INFO +1 -1
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/__init__.py +1 -1
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/asyncio/client.py +21 -4
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/client.py +21 -7
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/beatmap.py +3 -1
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/comment.py +3 -1
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/match.py +3 -1
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/score.py +12 -5
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/user.py +10 -3
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/path.py +4 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/util.py +3 -0
- {osu_py-2.2.2 → osu_py-2.3.0/osu.py.egg-info}/PKG-INFO +1 -1
- {osu_py-2.2.2 → osu_py-2.3.0}/LICENSE +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/MANIFEST.in +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/README.rst +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/asyncio/__init__.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/asyncio/http.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/auth.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/constants.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/enums.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/exceptions.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/http.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/notification.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/__init__.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/achievement.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/beatmapset_event.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/build.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/chat.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/current_user_attributes.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/discussion.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/event.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/forum.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/group.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/kudosu.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/multiplayer.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/news.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/notification.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/ranking.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/scope.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/seasonal_background.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/objects/wiki.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu/results.py +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/SOURCES.txt +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/dependency_links.txt +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/requires.txt +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/osu.py.egg-info/top_level.txt +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/pyproject.toml +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/requirements.txt +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/setup.cfg +0 -0
- {osu_py-2.2.2 → osu_py-2.3.0}/setup.py +0 -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: (
|
|
272
|
-
|
|
273
|
-
|
|
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: (
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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(
|
|
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(
|
|
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] =
|
|
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
|
-
|
|
310
|
-
try:
|
|
317
|
+
if data["type"] == "solo_score":
|
|
311
318
|
return SoloScore(data)
|
|
312
|
-
|
|
313
|
-
|
|
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"))(
|
|
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]
|
|
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
|
|
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
|