From 0c56b134e2ce0c343975d8f2c6f0ff072c6fb0dd Mon Sep 17 00:00:00 2001 From: Lyle Hayhurst Date: Wed, 6 Apr 2016 08:22:13 -0500 Subject: [PATCH] forking pychallonge; some sql file cleanup --- challonge/__init__.py | 4 + challonge/api.py | 132 ++++++++++++++++++ challonge/matches.py | 25 ++++ challonge/participants.py | 58 ++++++++ challonge/tournaments.py | 64 +++++++++ challonge_helper.py | 17 +++ email_fix.sql => dbs/email_fix.sql | 0 .../participant_count.sql | 0 requirements.txt | 1 - search.py | 11 +- season_zero_manual_entry.py | 0 xwlists.py | 6 +- 12 files changed, 310 insertions(+), 8 deletions(-) create mode 100644 challonge/__init__.py create mode 100644 challonge/api.py create mode 100644 challonge/matches.py create mode 100644 challonge/participants.py create mode 100644 challonge/tournaments.py rename email_fix.sql => dbs/email_fix.sql (100%) rename participant_count.sql => dbs/participant_count.sql (100%) create mode 100644 season_zero_manual_entry.py diff --git a/challonge/__init__.py b/challonge/__init__.py new file mode 100644 index 00000000..3869b8bd --- /dev/null +++ b/challonge/__init__.py @@ -0,0 +1,4 @@ +__version__ = "1.0.1" + +import tournaments, matches, participants +from api import set_credentials, get_credentials, fetch, ChallongeException diff --git a/challonge/api.py b/challonge/api.py new file mode 100644 index 00000000..a1c33597 --- /dev/null +++ b/challonge/api.py @@ -0,0 +1,132 @@ +import decimal +import urllib +import urllib2 +try: + from xml.etree import cElementTree as ElementTree +except ImportError: + from xml.etree import ElementTree + + +CHALLONGE_API_URL = "api.challonge.com/v1/" + +_credentials = { + "user": None, + "api_key": None, +} + + +class ChallongeException(Exception): + pass + + +def set_credentials(username, api_key): + """Set the challonge.com api credentials to use.""" + _credentials["user"] = username + _credentials["api_key"] = api_key + + +def get_credentials(): + """Retrieve the challonge.com credentials set with set_credentials().""" + return _credentials["user"], _credentials["api_key"] + + +def fetch(method, uri, params_prefix=None, **params): + """Fetch the given uri and return the contents of the response.""" + params = urllib.urlencode(_prepare_params(params, params_prefix)) + + # build the HTTP request + url = "https://%s/%s.xml" % (CHALLONGE_API_URL, uri) + print "fetching url " + url + if method == "GET": + req = urllib2.Request("%s?%s" % (url, params)) + else: + req = urllib2.Request(url) + req.add_data(params) + req.get_method = lambda: method + req.add_header("Accept", "application/xml"); + req.add_header("Content-Type", "application/xml"); + + # use basic authentication + user, api_key = get_credentials() + auth_handler = urllib2.HTTPBasicAuthHandler() + auth_handler.add_password( + realm="Application", + uri=req.get_full_url(), + user=user, + passwd=api_key + ) + opener = urllib2.build_opener(auth_handler) + + try: + response = opener.open(req) + except urllib2.HTTPError, e: + if e.code != 422: + raise + # wrap up application-level errors + doc = ElementTree.parse(e).getroot() + if doc.tag != "errors": + raise + errors = [e.text for e in doc] + raise ChallongeException(*errors) + return response + + +def fetch_and_parse(method, uri, params_prefix=None, **params): + """Fetch the given uri and return the root Element of the response.""" + doc = ElementTree.parse(fetch(method, uri, params_prefix, **params)) + return _parse(doc.getroot()) + + +def _parse(root): + """Recursively convert an Element into python data types""" + import dateutil.parser + if root.tag == "nil-classes": + return [] + elif root.get("type") == "array": + return [_parse(child) for child in root] + + d = {} + for child in root: + type = child.get("type") or "string" + + if child.get("nil"): + value = None + elif type == "boolean": + value = True if child.text.lower() == "true" else False + elif type == "datetime": + value = dateutil.parser.parse(child.text) + elif type == "decimal": + value = decimal.Decimal(child.text) + elif type == "integer": + value = int(child.text) + else: + value = child.text + + d[child.tag] = value + return d + + +def _prepare_params(dirty_params, prefix=None): + """Prepares parameters to be sent to challonge.com. + + The `prefix` can be used to convert parameters with keys that + look like ("name", "url", "tournament_type") into something like + ("tournament[name]", "tournament[url]", "tournament[tournament_type]"), + which is how challonge.com expects parameters describing specific + objects. + + """ + params = {} + for k, v in dirty_params.iteritems(): + if hasattr(v, "isoformat"): + v = v.isoformat() + elif isinstance(v, bool): + # challonge.com only accepts lowercase true/false + v = str(v).lower() + + if prefix: + params["%s[%s]" % (prefix, k)] = v + else: + params[k] = v + + return params diff --git a/challonge/matches.py b/challonge/matches.py new file mode 100644 index 00000000..9c8be8b5 --- /dev/null +++ b/challonge/matches.py @@ -0,0 +1,25 @@ +from challonge import api + + +def index(tournament, **params): + """Retrieve a tournament's match list.""" + return api.fetch_and_parse( + "GET", + "tournaments/%s/matches" % tournament, + **params) + + +def show(tournament, match_id): + """Retrieve a single match record for a tournament.""" + return api.fetch_and_parse( + "GET", + "tournaments/%s/matches/%s" % (tournament, match_id)) + + +def update(tournament, match_id, **params): + """Update/submit the score(s) for a match.""" + api.fetch( + "PUT", + "tournaments/%s/matches/%s" % (tournament, match_id), + "match", + **params) diff --git a/challonge/participants.py b/challonge/participants.py new file mode 100644 index 00000000..d186d054 --- /dev/null +++ b/challonge/participants.py @@ -0,0 +1,58 @@ +from challonge import api + + +def index(tournament): + """Retrieve a tournament's participant list.""" + return api.fetch_and_parse( + "GET", + "tournaments/%s/participants" % tournament) + +def create(tournament, name, **params): + """Add a participant to a tournament.""" + params.update({"name": name}) + + return api.fetch_and_parse( + "POST", + "tournaments/%s/participants" % tournament, + "participant", + **params) + + +def show(tournament, participant_id): + """Retrieve a single participant record for a tournament.""" + return api.fetch_and_parse( + "GET", + "tournaments/%s/participants/%s" % (tournament, participant_id)) + + +def update(tournament, participant_id, **params): + """Update the attributes of a tournament participant.""" + api.fetch( + "PUT", + "tournaments/%s/participants/%s" % (tournament, participant_id), + "participant", + **params) + + +def destroy(tournament, participant_id): + """Destroys or deactivates a participant. + + If tournament has not started, delete a participant, automatically + filling in the abandoned seed number. + + If tournament is underway, mark a participant inactive, automatically + forfeiting his/her remaining matches. + + """ + api.fetch( + "DELETE", + "tournaments/%s/participants/%s" % (tournament, participant_id)) + + +def randomize(tournament): + """Randomize seeds among participants. + + Only applicable before a tournament has started. + + """ + api.fetch("POST", "tournaments/%s/participants/randomize" % tournament) diff --git a/challonge/tournaments.py b/challonge/tournaments.py new file mode 100644 index 00000000..08c63808 --- /dev/null +++ b/challonge/tournaments.py @@ -0,0 +1,64 @@ +from challonge import api + + +def index(**params): + """Retrieve a set of tournaments created with your account.""" + return api.fetch_and_parse("GET", "tournaments", **params) + + +def create(name, url, tournament_type="single elimination", **params): + """Create a new tournament.""" + params.update({ + "name": name, + "url": url, + "tournament_type": tournament_type, + }) + + return api.fetch_and_parse("POST", "tournaments", "tournament", **params) + + +def show(tournament): + """Retrieve a single tournament record created with your account.""" + return api.fetch_and_parse("GET", "tournaments/%s" % tournament) + + +def update(tournament, **params): + """Update a tournament's attributes.""" + api.fetch("PUT", "tournaments/%s" % tournament, "tournament", **params) + + +def destroy(tournament): + """Deletes a tournament along with all its associated records. + + There is no undo, so use with care! + + """ + api.fetch("DELETE", "tournaments/%s" % tournament) + + +def publish(tournament): + """Publish a tournament, making it publically accessible. + + The tournament must have at least 2 participants. + + """ + api.fetch("POST", "tournaments/publish/%s" % tournament) + + +def start(tournament): + """Start a tournament, opening up matches for score reporting. + + The tournament must have at least 2 participants. + + """ + api.fetch("POST", "tournaments/start/%s" % tournament) + + +def reset(tournament): + """Reset a tournament, clearing all of its scores and attachments. + + You can then add/remove/edit participants before starting the + tournament again. + + """ + api.fetch("POST", "tournaments/reset/%s" % tournament) diff --git a/challonge_helper.py b/challonge_helper.py index 4306436e..e2c1f203 100644 --- a/challonge_helper.py +++ b/challonge_helper.py @@ -1,3 +1,5 @@ +import os +import unittest import challonge @@ -29,3 +31,18 @@ def match_index(self, tournament): def attachments_index(self, tournament, match_id): url = "tournaments/%s/matches/%s/attachments" % (tournament, match_id) return challonge.api.fetch_and_parse("GET", url) + +class challongeAPITest(unittest.TestCase): + + def __init__(self,*args, **kwargs): + super(challongeAPITest, self).__init__(*args, **kwargs) + challonge_user = os.getenv('CHALLONGE_USER') + challonge_key = os.getenv('CHALLONGE_API_KEY') + self.ch = ChallongeHelper(challonge_user, challonge_key) + + def testGetTournament(self): + i = self.ch.get_tournament("XWingVassalLeagueSeasonZero") + print i + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/email_fix.sql b/dbs/email_fix.sql similarity index 100% rename from email_fix.sql rename to dbs/email_fix.sql diff --git a/participant_count.sql b/dbs/participant_count.sql similarity index 100% rename from participant_count.sql rename to dbs/participant_count.sql diff --git a/requirements.txt b/requirements.txt index 1e172832..644ab645 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,4 +21,3 @@ wsgiref==0.1.2 whoosh==2.5.6 python-jsonschema-objects==0.0.15 iso8601==0.1.11 -pychallonge==1.0.1 diff --git a/search.py b/search.py index 906fb318..c9ea93c4 100644 --- a/search.py +++ b/search.py @@ -1,7 +1,7 @@ import re import unittest -from sqlalchemy import and_, or_, func +from sqlalchemy import and_, or_, func, not_ from whoosh.qparser import QueryParser from whoosh.query import Term @@ -15,10 +15,12 @@ def wildcard(term): expression_map = { 'AND' : and_, - 'OR' : or_ } + 'OR' : or_, + 'NOT' : not_} and_regex = re.compile( r'\s+and\s+') -or_regex = re.compile( r'\s+or\s+') +or_regex = re.compile( r'\s+or\s+') +not_regex = re.compile( r'\s+not\s+') PILOT_MATCH = "pilot_match" SHIP_MATCH = "ship_match" @@ -67,7 +69,7 @@ def tree_to_expr(tree, subq): fn = expression_map[tree.JOINT.strip()] return fn( *( - [tree_to_expr( child, subq) for child in tree ] + [tree_to_expr( child, subq) for child in tree ] ) ) @@ -102,6 +104,7 @@ def __init__(self, search_term): search_term = re.sub( and_regex, ' AND ', search_term ) search_term = re.sub( or_regex, ' OR ', search_term) + search_term = re.sub( not_regex, ' NOT ', search_term) parser = QueryParser("content", schema=None) q = parser.parse(search_term) diff --git a/season_zero_manual_entry.py b/season_zero_manual_entry.py new file mode 100644 index 00000000..e69de29b diff --git a/xwlists.py b/xwlists.py index 67a597a4..289a9554 100644 --- a/xwlists.py +++ b/xwlists.py @@ -161,7 +161,7 @@ def cache_xwvl_players(): print "caching league players" c = ChallongeHelper( myapp.challonge_user, myapp.challonge_key ) pm = PersistenceManager(myapp.db_connector) - league = pm.get_league("X-Wing Vassal League Season Zero") + league = pm.get_league("X-Wing Vassal League Season Zero Dot Five") bootstrap_league_players(c, league, pm) return redirect(url_for('league_players', league_id=league.id)) @@ -169,7 +169,7 @@ def cache_xwvl_players(): def league_players(): league_id = request.args.get('league_id') #TODO: unused for now, use when we have more than one league ( if ever ) pm = PersistenceManager(myapp.db_connector) - league = pm.get_league("X-Wing Vassal League Season Zero") + league = pm.get_league("X-Wing Vassal League Season Zero Dot Five") players = [] for tier in league.tiers: for division in tier.divisions: @@ -232,7 +232,7 @@ def league_admin(): def cache_league_results(): c = ChallongeHelper( myapp.challonge_user, myapp.challonge_key ) pm = PersistenceManager(myapp.db_connector) - league = pm.get_league("X-Wing Vassal League Season Zero") + league = pm.get_league("X-Wing Vassal League Season Zero Dot Five") for d in league.tiers[0].divisions: #TODO: iterate through tiers when we have tiers to go through print "fetching match results for division %s" % ( d.name )