Skip to content

Commit

Permalink
#318: add some exceptions and modify the interface of some methods
Browse files Browse the repository at this point in the history
  • Loading branch information
kmyk committed Feb 19, 2019
1 parent 9fd4dc2 commit 06456a9
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 41 deletions.
6 changes: 5 additions & 1 deletion onlinejudge/_implementation/command/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ def download(args: 'argparse.Namespace') -> None:
# get samples from the server
with utils.with_cookiejar(utils.new_default_session(), path=args.cookie) as sess:
if args.system:
samples = problem.download_system_cases(session=sess) # type: ignore
try:
samples = problem.download_system_cases(session=sess) # type: ignore
except onlinejudge.type.NotLoggedInError:
log.error('login required')
sys.exit(1)
else:
samples = problem.download_sample_cases(session=sess) # type: ignore

Expand Down
5 changes: 4 additions & 1 deletion onlinejudge/_implementation/command/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,7 @@ def get_credentials() -> Tuple[str, str]:
log.warning('If you don\'t want to give your password to this program, you can give only your session tokens.')
log.info('see: https://github.com/kmyk/online-judge-tools/blob/master/LOGIN_WITH_COOKIES.md')

service.login(get_credentials, session=sess, **kwargs) # type: ignore
try:
service.login(get_credentials, session=sess, **kwargs) # type: ignore
except onlinejudge.type.LoginError:
pass
3 changes: 3 additions & 0 deletions onlinejudge/_implementation/command/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ def submit(args: 'argparse.Namespace') -> None:
kwargs['kind'] = 'example'
try:
submission = problem.submit_code(code, language=args.language, session=sess, **kwargs) # type: ignore
except onlinejudge.type.NotLoggedInError:
log.failure('login required')
sys.exit(1)
except onlinejudge.type.SubmissionError:
log.failure('submission failed')
sys.exit(1)
Expand Down
26 changes: 20 additions & 6 deletions onlinejudge/service/atcoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import SubmissionError
from onlinejudge.type import LoginError, NotLoggedInError, SubmissionError


def _request(*args, **kwargs):
Expand All @@ -35,7 +35,11 @@ def _request(*args, **kwargs):

@utils.singleton
class AtCoderService(onlinejudge.type.Service):
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> bool:
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None:
"""
:raises LoginError:
"""

session = session or utils.new_default_session()
url = 'https://practice.contest.atcoder.jp/login'
# get
Expand All @@ -44,13 +48,19 @@ def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session:
for msg in msgs:
log.status('message: %s', msg)
if msgs:
return 'login' not in resp.url
if 'login' not in resp.url:
return # redirect means that you are already logged in
else:
raise LoginError('something wrong: ' + str(msgs))
# post
username, password = get_credentials()
resp = _request('POST', url, session=session, data={'name': username, 'password': password}, allow_redirects=False)
msgs = AtCoderService._get_messages_from_cookie(resp.cookies)
AtCoderService._report_messages(msgs)
return 'login' not in resp.url # AtCoder redirects to the top page if success
if 'login' not in resp.url:
pass # AtCoder redirects to the top page if success
else:
raise LoginError('your password may be not correct: ' + str(msgs))

def is_logged_in(self, session: Optional[requests.Session] = None) -> bool:
session = session or utils.new_default_session()
Expand Down Expand Up @@ -421,6 +431,10 @@ def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[
return language_dict

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.DummySubmission:
"""
:raises NotLoggedInError:
:raises SubmissionError:
"""
assert language in self.get_language_dict(session=session)
session = session or utils.new_default_session()
# get
Expand All @@ -433,7 +447,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
path = utils.normpath(urllib.parse.urlparse(resp.url).path)
if path.startswith('/login'):
log.error('not logged in')
raise SubmissionError
raise NotLoggedInError
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
form = soup.find('form', action=re.compile(r'^/submit\?task_id='))
Expand Down Expand Up @@ -462,7 +476,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
else:
log.failure('failure')
log.debug('redirected to %s', resp.url)
raise SubmissionError
raise SubmissionError('it may be a rate limit')

def _get_task_id(self, session: Optional[requests.Session] = None) -> int:
if self._task_id is None:
Expand Down
25 changes: 17 additions & 8 deletions onlinejudge/service/codeforces.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import SubmissionError
from onlinejudge.type import LoginError, NotLoggedInError, SubmissionError


@utils.singleton
class CodeforcesService(onlinejudge.type.Service):
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> bool:
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None:
"""
:raises LoginError:
"""
session = session or utils.new_default_session()
url = 'https://codeforces.com/enter'
# get
resp = utils.request('GET', url, session=session)
if resp.url != url: # redirected
log.info('You have already signed in.')
return True
return
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
form = soup.find('form', id='enterForm')
Expand All @@ -43,10 +46,9 @@ def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session:
resp.raise_for_status()
if resp.url != url: # redirected
log.success('Welcome, %s.', username)
return True
else:
log.failure('Invalid handle or password.')
return False
raise LoginError('Invalid handle or password.')

def is_logged_in(self, session: Optional[requests.Session] = None) -> bool:
session = session or utils.new_default_session()
Expand Down Expand Up @@ -130,7 +132,12 @@ def get_language_dict(self, session: Optional['requests.Session'] = None) -> Dic
language_dict[option.attrs['value']] = {'description': option.string}
return language_dict

def submit_code(self, code: bytes, language: str, session: Optional['requests.Session'] = None) -> onlinejudge.type.Submission: # or SubmissionError
def submit_code(self, code: bytes, language: str, session: Optional['requests.Session'] = None) -> onlinejudge.type.Submission:
"""
:raises NotLoggedInError:
:raises SubmissionError:
"""

session = session or utils.new_default_session()
# get
resp = utils.request('GET', self.get_url(), session=session)
Expand All @@ -139,7 +146,7 @@ def submit_code(self, code: bytes, language: str, session: Optional['requests.Se
form = soup.find('form', class_='submitForm')
if form is None:
log.error('not logged in')
raise SubmissionError
raise NotLoggedInError
log.debug('form: %s', str(form))
# make data
form = utils.FormSender(form, url=resp.url)
Expand All @@ -157,9 +164,11 @@ def submit_code(self, code: bytes, language: str, session: Optional['requests.Se
log.debug('redirected to %s', resp.url)
# parse error messages
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
msgs = [] # type: List[str]
for span in soup.findAll('span', class_='error'):
msgs += [span.string]
log.warning('Codeforces says: "%s"', span.string)
raise SubmissionError
raise SubmissionError('it may be the "You have submitted exactly the same code before" error: ' + str(msgs))

def get_url(self) -> str:
table = {}
Expand Down
31 changes: 24 additions & 7 deletions onlinejudge/service/hackerrank.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,24 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LabeledString, TestCase
from onlinejudge.type import LabeledString, LoginError, NotLoggedInError, SubmissionError, TestCase


@utils.singleton
class HackerRankService(onlinejudge.type.Service):
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> bool:
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None:
"""
:raises LoginError:
"""

session = session or utils.new_default_session()
url = 'https://www.hackerrank.com/auth/login'
# get
resp = utils.request('GET', url, session=session)
if resp.url != url:
log.debug('redirected: %s', resp.url)
log.info('You have already signed in.')
return True
return
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
csrftoken = soup.find('meta', attrs={'name': 'csrf-token'}).attrs['content']
Expand All @@ -54,10 +58,9 @@ def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session:
# result
if '/auth' not in resp.url:
log.success('You signed in.')
return True
else:
log.failure('You failed to sign in. Wrong user ID or password.')
return False
raise LoginError('You failed to sign in. Wrong user ID or password.')

def is_logged_in(self, session: Optional[requests.Session] = None) -> bool:
session = session or utils.new_default_session()
Expand Down Expand Up @@ -88,6 +91,9 @@ def __init__(self, contest_slug: str, challenge_slug: str):
self.challenge_slug = challenge_slug

def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[TestCase]:
"""
:raises NotImplementedError:
"""
log.warning('use --system option')
raise NotImplementedError

Expand Down Expand Up @@ -148,6 +154,10 @@ def from_url(cls, url: str) -> Optional['HackerRankProblem']:
return None

def _get_model(self, session: Optional[requests.Session] = None) -> Dict[str, Any]:
"""
:raises SubmissionError:
"""

session = session or utils.new_default_session()
# get
url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}'.format(self.contest_slug, self.challenge_slug)
Expand All @@ -157,7 +167,7 @@ def _get_model(self, session: Optional[requests.Session] = None) -> Dict[str, An
log.debug('json: %s', it)
if not it['status']:
log.error('get model: failed')
raise onlinejudge.type.SubmissionError
raise SubmissionError
return it['model']

def _get_lang_display_mapping(self, session: Optional[requests.Session] = None) -> Dict[str, str]:
Expand Down Expand Up @@ -193,7 +203,14 @@ def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[
return result

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
"""
:raises NotLoggedInError:
:raises SubmissionError:
"""

session = session or utils.new_default_session()
if not self.get_service().is_logged_in(session=session):
raise NotLoggedInError
# get
resp = utils.request('GET', self.get_url(), session=session)
# parse
Expand All @@ -209,7 +226,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
log.debug('json: %s', it)
if not it['status']:
log.failure('Submit Code: failed')
raise onlinejudge.type.SubmissionError
raise SubmissionError
model_id = it['model']['id']
url = self.get_url().rstrip('/') + '/submissions/code/{}'.format(model_id)
log.success('success: result: %s', url)
Expand Down
25 changes: 20 additions & 5 deletions onlinejudge/service/topcoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LoginError, NotLoggedInError, SubmissionError


@utils.singleton
class TopCoderService(onlinejudge.type.Service):
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> bool:
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None:
"""
:raises LoginError:
"""
session = session or utils.new_default_session()

# NOTE: you can see this login page with https://community.topcoder.com/longcontest/?module=Submit
Expand All @@ -42,10 +46,15 @@ def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session:

if 'longcontest' not in resp.url:
log.success('Success')
return True
else:
log.failure('Failure')
return False
raise LoginError

def is_logged_in(self, session: Optional[requests.Session] = None) -> bool:
"""
:raises NotImplementedError:
"""
raise NotImplementedError

def get_url(self) -> str:
return 'https://www.topcoder.com/'
Expand Down Expand Up @@ -110,11 +119,17 @@ def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[
def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None, kind: str = 'example') -> onlinejudge.type.Submission:
"""
:param kind: must be one of `example` (default) or `full`
:raises NotLoggedInError:
:raises SubmissionError:
"""

assert kind in ['example', 'full']
session = session or utils.new_default_session()

# TODO: implement self.is_logged_in()
# if not self.is_logged_in(session=session):
# raise NotLoggedInError

# module=MatchDetails
url = 'https://community.topcoder.com/tc?module=MatchDetails&rd=%d' % self.rd
resp = utils.request('GET', url, session=session)
Expand All @@ -129,7 +144,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
path = [tag.attrs['href'] for tag in soup.find_all('a', text='Submit') if ('rd=%d' % self.rd) in tag.attrs['href']]
if len(path) == 0:
log.error('link to submit not found: Are you logged in? Are you registered? Is the contest running?')
raise onlinejudge.type.SubmissionError
raise SubmissionError('something wrong')
assert len(path) == 1
path = path[0]
assert path.startswith('/') and 'module=Submit' in path
Expand Down Expand Up @@ -171,7 +186,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
messages = soup.find('textarea', {'name': 'messages'}).text
log.failure('%s', messages)
raise onlinejudge.type.SubmissionError
raise SubmissionError('it may be a rate limit: ' + messages)

def get_standings(self, session: Optional[requests.Session] = None) -> onlinejudge.type.Standings:
session = session or utils.new_default_session()
Expand Down
Loading

0 comments on commit 06456a9

Please sign in to comment.