Releases: tybug/ossapi
v2.2.0
Note that this release includes a few breaking changes for OssapiV2
. They are bolded below.
- make
UserStatistics.play_time
optional - make
BeatmapCompact.beatmapset_id
non-optional - add
OssapiV2#news_listing
- add
OssapiV2#news_post
- add
OssapiV2#friends
- add
OssapiV2#seasonal_backgrounds
- add
OssapiV2#beatmapset
to retrieve a beatmapset from either a beatmap id, or a beatmapset id - implement
Beatmapset#expand
in line withBeatmap#expand
andUser#expand
- rename
OssapiV2#beatmapsets_events
toOssapiV2#beatmapset_events
- rename Search.{user, wiki_page} to {users, wiki_pages}
- add "foreign key" methods to retrieve the related
User
,Beatmap
, orBeatmapset
of any model. See the "following foreign keys" section of the readme for more information.- note that this is a breaking change because some attributes which were previously properties are now methods, and will need to be adjusted accordingly. For instance, if you were previously accessing
bmset = beatmap.beatmapset
, you will need to change that to callingbmset = beatmap.beatmapset()
instead. This comes with added benefits like being guaranteed non-null.
- note that this is a breaking change because some attributes which were previously properties are now methods, and will need to be adjusted accordingly. For instance, if you were previously accessing
- add custom model json encoder
ModelEncoder
and correspondingserialize_model
method to serialize a model to json (from ossapi import serialize_model
) - remove explicit annotations from
Cursor
- this means that cursor attributes will be their "raw" json type, and not converted to a nice type like a
datetime
orMod
- the osu!web team has made it very clear that cursors are part of an internal, undocumented api, which is why I'm not putting any effort into making it easily accessible for consumers. If you need to access a cursor object for some reason, you should probably figure out another way or raise it with the web team.
- this means that cursor attributes will be their "raw" json type, and not converted to a nice type like a
- don't raise error on null fields, even when they're non-optional, unless
strict=True
- this better preserves forward-compatibility for all releases from now on. The osu! api sometimes makes previously required arguments optional. Previously, this would cause fatal errors when decoding. Now we simply ignore such mismatches.
- you can and should still rely on non-null guarantees of fields not marked as optional, as I will still fix such issues if the api does ever break compatibility in this way. This change is only to improve forward-compatibility for old releases.
v2.1.0
- OssapiV2 is now compatible with python 3.7+ (and likely also python 3.6+, but I have not tested this yet). Previously we required python 3.8+
- allow multiple instances of
OssapiV2
with different scopes, client ids, secrets, or any combination thereof to coexist and keep separate cached authentication - fix expired client credentials tokens not refreshing (could occur in a long lived script, ie >24 hours)
- remove
OssapiV2#beatmap_lookup
, useOssapiV2#beatmap
instead
- remove
- return a new
Replay
object fromOssapiV2#download_replay
instead of saving to a temporary file- this is a thin shim around an
osrparse.Replay
object, but with a few additional convenience methods and type conversions. See theossapi.Replay
documentation for details
- this is a thin shim around an
- add ability to pass user/beatmap models in place of a user id or beatmap id respectively. See the readme (under "advanced usage") for details
- add
Grant
parameter toOssapiV2
, which can be used to override the usual automatic detection of the grant type, which depends on whetherredirect_uri
was passed or not - add
OssapiV2#create_pm
endpoint - add
OssapiV2#beatmapset_discussions
endpoint - add
BeatmapsetCompact.track_id
attribute - add
BeatmapsetDiscussion.current_user_attributes
attribute - add
BeatmapCompact.beatmapset_id
attribute - add
BeatmapsetEventType.NOMINATION_RESET_RECEIVED
event parsing - raise
InsufficientScopeError
when attempting to access an endpoint you don't have the required scopes for UserCompact
andBeatmapCompact
are now "expandable", which means they have a new method#expand
which returns the corresponding fullUser
orBeatmap
object respectively. See the readme for more details on expandable models, under "advanced usage"- rename
search_beatmaps
tosearch_beatmapsets
- add
token_directory
andtoken_key
parameters toOssapiV2
:token_directory
: If passed, the given directory will be used to store and retrieve token files instead of locally wherever ossapi is installed. Useful if you want more control over token filestoken_key
: If passed, the given key will be used to name the token file instead of an automatically generated one. Note that if you pass this, you are taking responsibility for making sure it is unique / unused, and also for remembering the key you passed if you wish to eg remove the token in the future, which requires the key
v2.0.0
This release does two things: brings api v2 (OssapiV2
) support out of beta, and completely rewrites api v1 (Ossapi
).
If you were only using OssapiV2
, this release is your typical bugfix release and you can freely update without worrying about breaking changes. However, if you were using Ossapi
, this release contains significant breaking changes and you will need to rewrite your scripts accordingly. See the "ossapi v1 breaking changes" section for guidance on upgrading to v2.0.0.
OssapiV2 Changes
- implement token refreshing for client credentials grant. See #21 for details
- make
Beatmap.bpm
optional
Ossapi(V1) Breaking Changes
methods take better arguments
All methods now take positional arguments instead of a dict of kwargs. A full list of methods: get_beatmaps
, get_match
, get_scores
, get_replay
, get_user
, get_user_best
, get_user_recent
.
# v1.x.x
api.get_user({"u": username, "type": "string"})
# v2.0.0
api.get_user(username, user_type="string")
methods return models
All methods now return models instead of dicts. A full list of the new models: Beatmap
, User
, Event
, Score
, MatchInfo
, Match
, MatchGame
, and MatchScore
. This means that instead of indexing to access attributes, you should use dot access instead:
# v1.x.x
api.get_user_best({"m": "0", "u": user_id, "limit": 10})[0]["enabled_mods"]
# v2.0.0
api.get_user_best(user_id, mode=0, limit=10)[0].mods
This also means that all attributes will be their proper type instead of just strings:
# v1.x.x
score = api.get_user_best({"m": "0", "u": 12092800, "limit": 10})[0]
print(type(score["enabled_mods"]), type(score["countkatu"]), type(score["perfect"]))
# <class 'str'> <class 'str'> <class 'str'>
# v2.x.x
score = api.get_user_best(12092800, mode=0, limit=10)[0]
print(type(score.mods), type(score.count_katu), type(score.perfect))
# <class 'ossapi.mod.Mod'> <class 'int'> <class 'bool'>
(You may be wondering what ossapi.mod.Mod
is; see new mod class for details)
ratelimiting and error handling
Ossapi
now handles ratelimiting and other errors better:
- if we get ratelimited,
Ossapi
will sleep the current thread for the remaining amount of time until our ratelimits get reset. - if the api key is invalid, we will raise an
InvalidKeyException
. - if a replay being requested is unavailable, we will raise a
ReplayUnavailableException
. - if the api returns any other error, we will raise an
APIException
. This should never happen (we handle every known error that api v1 can throw), but is left as a failsafe. - if a
RequestException
is thrown when we make a request, we will sleep the thread for 5 seconds and then retry the request. - if the api returns invalid json (a
JSONDecodeError
error is thrown while attempting to decode the response), we will sleep the thread for 3 seconds and then retry the request, since this error is always a result of a temporary problem on the api's end.
Author's note: I realize that sleeping for x seconds on ratelimit is a naive implementation, but it has worked well enough so far. If you would benefit from a more sophisticated approach, please open an issue! I am much more likely to improve this if I know others will benefit from it. In the meantime, if you would like to customize ratelimiting, you can subclass Ossapi
and reimplement _enforce_ratelimit
.
new mod class
For convenience when working with mods, we provide a new Mod
class, which is used wherever the api returns a mod value. An overview of its methods, in example format:
from ossapi import Mod, Ossapi
api = Ossapi("key")
mods = api.get_scores(221777)[0].mods
# Mod's __str__ uses short_name()
print(mods)
print(mods.short_name())
# to break down a mod into its component mods (eg if you want ["HD", "DT"] from "HDDT")
print(mods.decompose())
# to get the long form name (HD -> Hidden)
print(mods.long_name())
# to access the underlying value
print(mods.value)
# to add or remove a mod from the mod combination, use + and -
print(mods + Mod.FL)
print(mods - Mod.HD)
# you can also add or remove multiple mods at a time
print(mods - Mod.HDHR)
# common mod combinations are stored as static variables under `Mod` for convenience
print(Mod.HDDT, Mod.HDHR, Mod.HDDTHR)
# otherwise, the preferred way to build up mods is by adding them together
print(Mod.HD + Mod.FL + Mod.EZ)
# alternatively, you can instantiate with the raw value
print(Mod(1034))
assert Mod.HD + Mod.FL + Mod.EZ == Mod(1034)
v2.0.0beta10
- add
message
attribute toChangelogEntry
- add
nominator_ids
attribute toBeatmapsetEventType.DISQUALIFY
andBeatmapsetEventType.NOMINATION_RESET
models - rename
OssapiV2#topic
toOssapiV2#forum_topic
- fully implement
OssapiV2#forum_topic
- add
multiplayer_scores
endpoint - add
revoke_token
endpoint - add
beatmapset_discussion_votes
endpoint - rename
BeatmapsetDiscussionPostResult
toBeatmapsetDiscussionPosts
- make
BeatmapSearchResult.recommended_difficulty
optional
v2.0.0beta9
- return a null cursor instead of erroring when no more pages are left on paginated endpoints
v2.0.0beta8
- implement
OssapiV2#changelog_build
,OssapiV2#changelog_listing
, andOssapiV2#changelog_lookup
endpoints - make
UserStatistics.global_rank
optional - make
Score.best_id
optional - rename
OssapiV2#user
param fromuser_id
touser
- add
Score.passed
attribute
v2.0.0beta7
- add
api#wiki_page
endpoint - add new attributes (
pending_beatmapset_count
andranked_beatmapset_count
) toUserCompact
- make
UserGroup.description
optional to account for breaking api change - add
key
parameter toapi#get_user
(specifying if the passeduser_id
is an id or a username) - rename
UserBeatmapType.RANKED_AND_APPROVED
toRANKED
andUserBeatmapType.UNRANKED
toPENDING
- add annotations to
api#beatmapsets_events
- add basic tests
v2.0.0beta6
- fix
api#search_beatmaps
method not taking the right parameters / not working properly - new fields being added to the osu api will no longer break old ossapi versions, as we now silently drop those attributes
- if you would like to raise on unexpected attributes instead (old behavior), pass
strict=True
toOssapiV2
v2.0.0beta5
- add new attributes to many models
v2.0.0beta4
- add
beatmapset_discussion_posts
anduser_recent_activity
endpoints - update
user_beatmaps
return type - update
BeatmapPlaycount
model to match updated docs