Skip to content

Releases: tybug/ossapi

v2.2.0

28 Oct 02:04
Compare
Choose a tag to compare

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 with Beatmap#expand and User#expand
  • rename OssapiV2#beatmapsets_events to OssapiV2#beatmapset_events
  • rename Search.{user, wiki_page} to {users, wiki_pages}
  • add "foreign key" methods to retrieve the related User, Beatmap, or Beatmapset 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 calling bmset = beatmap.beatmapset() instead. This comes with added benefits like being guaranteed non-null.
  • add custom model json encoder ModelEncoder and corresponding serialize_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 or Mod
    • 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.
  • 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

08 Oct 03:01
Compare
Choose a tag to compare
  • 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, use OssapiV2#beatmap instead
  • return a new Replay object from OssapiV2#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 the ossapi.Replay documentation for details
  • 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 to OssapiV2, which can be used to override the usual automatic detection of the grant type, which depends on whether redirect_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 and BeatmapCompact are now "expandable", which means they have a new method #expand which returns the corresponding full User or Beatmap object respectively. See the readme for more details on expandable models, under "advanced usage"
  • rename search_beatmaps to search_beatmapsets
  • add token_directory and token_key parameters to OssapiV2:
    • 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 files
    • token_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

29 Aug 01:22
Compare
Choose a tag to compare

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

06 Aug 00:19
Compare
Choose a tag to compare
  • add message attribute to ChangelogEntry
  • add nominator_ids attribute to BeatmapsetEventType.DISQUALIFY and BeatmapsetEventType.NOMINATION_RESET models
  • rename OssapiV2#topic to OssapiV2#forum_topic
  • fully implement OssapiV2#forum_topic
  • add multiplayer_scores endpoint
  • add revoke_token endpoint
  • add beatmapset_discussion_votes endpoint
  • rename BeatmapsetDiscussionPostResult to BeatmapsetDiscussionPosts
  • make BeatmapSearchResult.recommended_difficulty optional

v2.0.0beta9

21 Jul 16:30
Compare
Choose a tag to compare
  • return a null cursor instead of erroring when no more pages are left on paginated endpoints

v2.0.0beta8

16 Jul 01:25
Compare
Choose a tag to compare
  • implement OssapiV2#changelog_build, OssapiV2#changelog_listing, and OssapiV2#changelog_lookup endpoints
  • make UserStatistics.global_rank optional
  • make Score.best_id optional
  • rename OssapiV2#user param from user_id to user
  • add Score.passed attribute

v2.0.0beta7

25 Jun 03:18
Compare
Choose a tag to compare
  • add api#wiki_page endpoint
  • add new attributes (pending_beatmapset_count and ranked_beatmapset_count) to UserCompact
  • make UserGroup.description optional to account for breaking api change
  • add key parameter to api#get_user (specifying if the passed user_id is an id or a username)
  • rename UserBeatmapType.RANKED_AND_APPROVED to RANKED and UserBeatmapType.UNRANKED to PENDING
  • add annotations to api#beatmapsets_events
  • add basic tests

v2.0.0beta6

10 Jun 17:38
Compare
Choose a tag to compare
  • 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 to OssapiV2

v2.0.0beta5

09 Jun 16:53
Compare
Choose a tag to compare
  • add new attributes to many models

v2.0.0beta4

21 May 04:55
Compare
Choose a tag to compare
  • add beatmapset_discussion_posts and user_recent_activity endpoints
  • update user_beatmaps return type
  • update BeatmapPlaycount model to match updated docs