Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Renondedju committed Jul 13, 2018
2 parents 1348efb + bd02de8 commit 42fd510
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 36 deletions.
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

> Osu<span></span>.py library changelogs
## Version 0.4.0

> .osu file download !
- Added the BeatmapFile Model
- Added OsuApi.get_beatmap_file()

## Version 0.3.1

- Unavaliable requested requests no longer raise ReplayUnavailable exception
Expand Down
3 changes: 2 additions & 1 deletion pyosu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
__author__ = 'Renondedju'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Renondedju'
__version__ = '0.3.1'
__version__ = '0.4.0'

from .api import OsuApi
from .user import User
Expand All @@ -47,6 +47,7 @@
from .exceptions import *
from .game_modes import GameMode
from .user_recent import UserRecent
from .beatmap_file import BeatmapFile
from .scoring_type import ScoringType
from .beatmap_genre import BeatmapGenre
from .game_modifiers import GameModifier
Expand Down
27 changes: 23 additions & 4 deletions pyosu/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
from .beatmap import Beatmap
from .user_best import UserBest
from .user_event import UserEvent
from .exceptions import ReplayUnavailable
from .user_recent import UserRecent
from .beatmap_file import BeatmapFile
from .score_collection import ScoreCollection
from .multiplayer_game import MultiplayerGame
from .multiplayer_score import MultiplayerScore
Expand All @@ -47,10 +49,7 @@ def __init__(self, api_key : str):

async def __get_data(self, url : str, unique = True, **args):

route = Route(url, self._api_key)

for key, value in args.items():
route.add_param(key, value)
route = Route(url, self._api_key, **args)

request = Request(route)
await request.fetch(self._session)
Expand Down Expand Up @@ -359,3 +358,23 @@ async def get_match(self, match_id):
games.append(MultiplayerGame(self, scores, **game))

return MultiplayerMatch(self, games, **data.get('match', {}))

async def get_beatmap_file(self, beatmap_id):
"""
This model is way heavier than a classic Beatmap object (3Kb to 1Mb) since
it contains the beatmap file. If you don't really need it, don't use it !
Parameters :
beatmap_id - the beatmap ID (not beatmap set ID!) (requiered).
"""

route = Route(base = 'https://osu.ppy.sh/osu/', path = str(beatmap_id))
request = Request(route, 1, False)

await request.fetch()

if request.data == '':
return BeatmapFile(self, **{})

return BeatmapFile(self, **{"content": request.data})
70 changes: 70 additions & 0 deletions pyosu/beatmap_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# MIT License

# Copyright (c) 2018 Renondedju

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import re

from .base_model import BaseModel

class BeatmapFile(BaseModel):
""" Beatmap file model.
This model is way heavier than a classic Beatmap object (3Kb to 1Mb) since
it contains the beatmap file. If you don't really need it, don't use it !
"""

def __init__(self, api : 'OsuApi', **data):

super().__init__(api, **data)

self.content = data.get('content', '')
self.version = self.parse_version()

def parse_version(self):
""" Parses the version of the file """

regex = r"osu file format v(\d*)"
matches = re.search(regex, self.content, re.IGNORECASE)

if matches:
return int(matches.group(1))

return 0

def get_category(self, category_name : str):
""" Gets a file category
Example :
calling get_category('Editor') on a file with version == 14
might return something like :
'DistanceSpacing: 0.9
BeatDivisor: 2
GridSize: 4
TimelineZoom: 1.399999'
"""

try:
start = self.content.index(f'[{category_name}]') + len(category_name) + 2
end = self.content.index('[', start )
return self.content[start:end]
except ValueError:
return ""
50 changes: 24 additions & 26 deletions pyosu/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,32 @@

class Route:

BASE = 'https://osu.ppy.sh/api/'

def __init__(self, path : str, api_key : str, **parameters):
def __init__(self, path : str = '', api_key : str = '', base = 'https://osu.ppy.sh/api/', **parameters):

self.base = base
self.path = path
self.api_key = api_key
self.parameters = parameters
self.parameters = {}

for key, value in parameters.items():
if value != None:
self.parameters[key] = value

@property
def route(self):
""" Returns the current route """

params = ''
params = []
if self.api_key != '':
params.append(f'k={self.api_key}')

for key, value in self.parameters.items():
params += f'&{str(key)}={str(value)}'
params.append(f'{str(key)}={str(value)}')

return f"{Route.BASE}{self.path}?k={self.api_key}{params}"
if len(params) > 0:
return f"{self.base}{self.path}?{'&'.join(params)}"

return f"{self.base}{self.path}"

def add_param(self, key, value):
""" Adds or updates a prameter """
Expand Down Expand Up @@ -76,27 +85,14 @@ def check_params(self):

return

def check_path(self):
""" Raise the RouteNotFound Exception if the path isn't in the following list :
[get_beatmaps, get_user, get_scores, get_user_best, get_user_recent, get_match, get_replay]
"""

accepted_paths = ['get_beatmaps', 'get_user', 'get_scores',
'get_user_best', 'get_user_recent', 'get_match', 'get_replay']

if self.path not in accepted_paths:
raise RouteNotFound(f'The route {self.route} does not exists.', 404)

return

class Request():

def __init__(self, route : Route, retry : int = 5):
def __init__(self, route : Route, retry : int = 5, json_response : bool = True):

self.retry_count = retry # Number of retrys to do before throwing any errors
self.route = route
self._data = []
self.json_response = json_response
self.retry_count = retry # Number of retrys to do before throwing any errors
self.route = route
self._data = []

@property
def data(self):
Expand All @@ -106,6 +102,9 @@ async def get_json(self, response, *args):
""" Returns the json version of an api response """
text = await response.text()

if self.json_response is False:
return text

if (text == None or text == ''):
return {}

Expand All @@ -126,7 +125,6 @@ async def fetch_with_session(self, session):
""" Fetches some data with a session using the actual route """

self.route.check_params()
self.route.check_path()

async with session.get(self.route.route) as response:

Expand Down
13 changes: 8 additions & 5 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,20 +107,22 @@ async def test_user_recents():
async def test_replay():

#This replay is one year old and shouldn't be avaliable
try:
await api.get_replay(GameMode.Osu, 390057, 'Renondedju')
except ReplayUnavailable:
pass
await api.get_replay(GameMode.Osu, 390057, 'Renondedju')

# Cannot really test replays since they might be deleted at all time ..
# Also sice the request rate is at 10/min, I don't wanna abuse it

async def test_match():

await api.get_match(0)

# Cannot test too much things here since a match is temporary

async def test_beatmap_file():

b = await api.get_beatmap_file(390057)
if b.is_empty:
raise ValueError('The beatmap file shouldn\'t be empty')

async def main():

await test(test_user)
Expand All @@ -132,6 +134,7 @@ async def main():
await test(test_user_bests)
await test(test_user_recent)
await test(test_user_recents)
await test(test_beatmap_file)
await test(test_score_collection)
await test(test_beatmap_collection)

Expand Down

0 comments on commit 42fd510

Please sign in to comment.