1
0
codehunt-data-py/codehunt/rest.py

259 lines
9.9 KiB
Python

import json
import requests
import time
class ExplorationTestCase(object):
'''Wrapper for the test case part of an Exploration.'''
def __init__(self, names, test_case):
self._names = names
# See https://api.codehunt.com/ for documentation on interface TestCase
self.test_case = test_case
# one of the following: "Failure", "Inconclusive", "Success"
self.status = self.test_case['status']
self.any_exception_or_path_bounds_exceeded =\
self.test_case['anyExceptionOrPathBoundsExceeded']
self.summary = self.test_case['summary']
self.message = self.test_case['message']
self.exception = self.test_case['exception']
self.stack_trace = self.test_case['stackTrace']
if names and test_case['values']:
self.values_dict = dict(zip(names, test_case['values']))
self.names = list(names) # copy names because we mutate it
self.values = test_case['values']
if 'EXPECTED RESULT' in self.values_dict:
self.expected = self.values_dict['EXPECTED RESULT']
del self.values_dict['EXPECTED RESULT']
idx = self.names.index('EXPECTED RESULT')
del self.names[idx]
del self.values[idx]
else:
self.expected = None
if 'YOUR RESULT' in self.values_dict:
self.actual = self.values_dict['YOUR RESULT']
del self.values_dict['YOUR RESULT']
idx = self.names.index('YOUR RESULT')
del self.names[idx]
del self.values[idx]
else:
self.actual = None
for name in self.names:
if ' ' in name:
print(name)
else:
self.names = None
self.values = None
self.actual = None
self.expected = None
print(self.test_case)
def __repr__(self):
return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
repr(self._names), repr(self.test_case))
def __str__(self):
params = ', '.join(['%s=%s' % (param, value)
for (param, value)
in zip(self.names, self.values)])
correct = 'Puzzle(%(params)s)%(expected)s' % {
'params': params,
'expected': ' = %s' % self.expected if self.expected else ''
}
if self.summary == 'Mismatch':
return "%(summary)s: %(correct)s (code returned %(actual)s)" % {
'summary': self.summary,
'actual': self.actual,
'correct': correct,
}
elif self.summary == '' and self.status == 'Success':
return "Success: %(correct)s" % {
'correct': correct,
}
elif self.exception:
return "Exception: %(correct)s (code threw %(exception)s)" % {
'correct': correct,
'exception': self.exception,
}
elif self.summary == 'path bounds exceeded (path bounds exceeded)':
return "Inconclusive: %(correct) (path bounds exceeded)"
else:
# Shouldn't get here, but say something meaningful if the above
# is missing a case.
return "%s: %s" % (self.status, correct)
def compilation_error_to_string(error):
return '%(line)d:%(column)d::%(errorNumber)s: %(errorText)s' % error
class Exploration(object):
'''Wrapper for /api/explorations response.'''
def __init__(self, attempt, exp):
self.attempt = attempt
# See https://api.codehunt.com/ for documentation on
# interface Exploration
self.exp = exp
self.is_complete = exp['isComplete']
self.kind = exp['kind']
self.attempt_compiles = self.kind == 'TestCases'
if self.attempt_compiles:
self.has_won = exp['hasWon']
self.test_cases = [ExplorationTestCase(exp['names'], tc)
for tc in exp['testCases']]
self.errors = None
else:
self.has_won = False
self.test_cases = None
if self.kind == 'InternalError':
# Should not happen
self.errors = [exp['exception']]
elif self.kind == 'CompilationError':
self.compilation_errors = exp['errors']
self.errors = [compilation_error_to_string(error)
for error in exp['errors']]
elif self.kind == 'BadPuzzle':
self.errors = [exp['description']]
elif self.kind == 'BadCodingDuel':
self.errors = exp['errors']
elif self.kind == 'BadDependency':
self.errors = exp['referencedTypes']
def __repr__(self):
return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
repr(self.attempt), repr(self.exp))
def __str__(self):
if self.kind == 'TestCases':
return "{Exploration %(kind)s%(won)s [%(test_cases)s]}" % {
'kind': self.kind,
'test_cases': '; '.join([str(tc)
for tc in self.test_cases]),
'won': ' (won)' if self.has_won else ''
}
else:
return "{Exploration %(kind)s %(errors)s}" % {
'kind': self.kind,
'errors': self.errors,
}
class Translation(object):
'''Wrapper for /api/translate response.'''
def __init__(self, attempt, translation):
self.attempt = attempt
# Include level so a translation object is a valid attempt for
# the Client.explore() method
self.level = attempt.level
self.translation = translation
if translation['kind'] == 'Translated':
self.success = True
self.text = translation['program']['text']
self.language = translation['program']['language']
self.errors = None
else:
self.success = False
self.text = None
self.language = None
self.errors = translation['errors']
def __repr__(self):
return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
repr(self.attempt), repr(self.translation))
def __str__(self):
if self.success:
return '{Translation of %s}' % self.attempt
else:
return '{Failed translation of %s: %s}' % \
(self.attempt, [compilation_error_to_string(error)
for error in self.errors])
class Client(object):
'''Client for Code Hunt REST API. See https://api.codehunt.com/ for
documentation on the API.'''
base_url = 'https://api.codehunt.com/api'
def __init__(self, client_id, client_secret):
'''client_id and client_secret are the Code Hunt REST API equivalent
of a username and password. If you do not have a client_id and
client_secret, you can request them from codehunt@microsoft.com.'''
self.client_id = client_id
self.client_secret = client_secret
self.headers = self._get_auth_header()
def _get_auth_header(self):
resp = requests.post("%s/token" % self.base_url,
params = { 'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret })
token = resp.json()['access_token']
return { 'Authorization': 'Bearer %s' % token }
def explore(self, attempt, wait=False):
'''Perform an exploration on an attempt finding one of three cases:
1. It has errors.
2. It is a correct solution.
3. It is incorrect, along with counterexamples showing that.
Returns a value of type Exploration.'''
resp = requests.post("%s/explorations" % self.base_url,
headers = self.headers,
data = json.dumps({
'program': {
'language': attempt.language,
'text': attempt.text
},
'challengeId': attempt.level.challenge_id,
}))
data = resp.json()
id = data['id']
get_exp_url = "%s/explorations/%s" % (self.base_url, id)
data = requests.get(get_exp_url, headers = self.headers).json()
# Don't wait for computation because any explorations in the
# data release should be cached and be available immediately.
if wait:
while not data['isComplete']:
time.sleep(1)
data = requests.get(get_exp_url, headers = self.headers).json()
return Exploration(attempt, data)
def translate(self, attempt):
'''Translates a Java program to C#. Note that the translation is very
limited in its Java support. The result of a translation is either
1. An error due to the code not being valid Java or using
unsupported features of Java.
2. The C# translation of the Java program which Code Hunt uses
internally.
Returns a value of type Translation.'''
if attempt.language != "Java":
raise Exception("Can only translate Java programs.")
resp = requests.post("%s/translate?language=CSharp" % self.base_url,
headers = self.headers,
data = json.dumps({
'language': attempt.language,
'text': attempt.text
}))
data = resp.json()
return Translation(attempt, data)