Skip to content

Commit

Permalink
Merge pull request #345 from kmyk/feature/make-language-id-type
Browse files Browse the repository at this point in the history
#318: improve the interface around submission
  • Loading branch information
fukatani authored Feb 28, 2019
2 parents a01051d + 2eb6fa2 commit b5af924
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 68 deletions.
13 changes: 7 additions & 6 deletions onlinejudge/_implementation/command/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import onlinejudge._implementation.download_history
import onlinejudge._implementation.logging as log
import onlinejudge._implementation.utils as utils
from onlinejudge.type import *

if TYPE_CHECKING:
import argparse
Expand Down Expand Up @@ -60,7 +61,7 @@ def submit(args: 'argparse.Namespace') -> None:

with utils.with_cookiejar(utils.new_default_session(), path=args.cookie) as sess:
# guess or select language ids
langs = problem.get_language_dict(session=sess)
langs = {language.id: {'description': language.name} for language in problem.get_available_languages(session=sess)} # type: Dict[LanguageId, Dict[str, str]]
matched_lang_ids = None # type: Optional[List[str]]
if args.language in langs:
matched_lang_ids = [args.language]
Expand Down Expand Up @@ -89,7 +90,7 @@ def submit(args: 'argparse.Namespace') -> None:
# report selected language ids
if matched_lang_ids is not None and len(matched_lang_ids) == 1:
args.language = matched_lang_ids[0]
log.info('chosen language: %s (%s)', args.language, langs[args.language]['description'])
log.info('chosen language: %s (%s)', args.language, langs[LanguageId(args.language)]['description'])
else:
if matched_lang_ids is None:
log.error('language is unknown')
Expand All @@ -101,7 +102,7 @@ def submit(args: 'argparse.Namespace') -> None:
log.error('Matched languages were not narrowed down to one.')
log.info('You have to choose:')
for lang_id in sorted(matched_lang_ids or langs.keys()):
log.emit('%s (%s)', lang_id, langs[lang_id]['description'])
log.emit('%s (%s)', lang_id, langs[LanguageId(lang_id)]['description'])
sys.exit(1)

# confirm
Expand Down Expand Up @@ -138,11 +139,11 @@ def submit(args: 'argparse.Namespace') -> None:
else:
kwargs['kind'] = 'example'
try:
submission = problem.submit_code(code, language=args.language, session=sess, **kwargs) # type: ignore
except onlinejudge.type.NotLoggedInError:
submission = problem.submit_code(code, language_id=LanguageId(args.language), session=sess, **kwargs) # type: ignore
except NotLoggedInError:
log.failure('login required')
sys.exit(1)
except onlinejudge.type.SubmissionError:
except SubmissionError:
log.failure('submission failed')
sys.exit(1)

Expand Down
27 changes: 15 additions & 12 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 LoginError, NotLoggedInError, SubmissionError
from onlinejudge.type import *


def _request(*args, **kwargs):
Expand Down Expand Up @@ -433,33 +433,36 @@ def get_input_format(self, session: Optional[requests.Session] = None) -> str:
return s
return ''

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, onlinejudge.type.Language]:
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]:
"""
:raises NotLoggedInError:
"""
session = session or utils.new_default_session()
# get
url = 'http://{}.contest.atcoder.jp/submit'.format(self.contest_id)
resp = _request('GET', url, session=session)
msgs = AtCoderService._get_messages_from_cookie(resp.cookies)
if AtCoderService._report_messages(msgs, unexpected=True):
return {}
return []
# check whether logged in
path = utils.normpath(urllib.parse.urlparse(resp.url).path)
if path.startswith('/login'):
log.error('not logged in')
return {}
raise NotLoggedInError
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
select = soup.find('select', class_='submit-language-selector') # NOTE: AtCoder can vary languages depending on tasks, even in one contest. here, ignores this fact.
language_dict = {}
languages = [] # type: List[Language]
for option in select.find_all('option'):
language_dict[option.attrs['value']] = {'description': option.string}
return language_dict
languages += [Language(option.attrs['value'], option.string)]
return languages

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.DummySubmission:
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> onlinejudge.type.DummySubmission:
"""
:raises NotLoggedInError:
:raises SubmissionError:
"""
assert language in self.get_language_dict(session=session)
assert language_id in [language.id for language in self.get_available_languages(session=session)]
session = session or utils.new_default_session()
# get
url = 'http://{}.contest.atcoder.jp/submit'.format(self.contest_id) # TODO: use beta.atcoder.jp
Expand All @@ -484,7 +487,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
form = utils.FormSender(form, url=resp.url)
form.set('task_id', str(task_id))
form.set('source_code', code)
form.set('language_id_{}'.format(task_id), language)
form.set('language_id_{}'.format(task_id), str(language_id))
resp = form.request(session=session)
resp.raise_for_status()
# result
Expand Down Expand Up @@ -628,8 +631,8 @@ def get_url(self, type: Optional[str] = None, lang: Optional[str] = None) -> str
def get_service(self) -> AtCoderService:
return AtCoderService()

def download(self, session: Optional[requests.Session] = None) -> str:
return self.get_source_code(session=session).decode()
def download_code(self, session: Optional[requests.Session] = None) -> bytes:
return self.get_source_code(session=session)

def _load_details(self, session: Optional[requests.Session] = None) -> None:
session = session or utils.new_default_session()
Expand Down
23 changes: 13 additions & 10 deletions onlinejudge/service/codeforces.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LoginError, NotLoggedInError, SubmissionError
from onlinejudge.type import *


@utils.singleton
Expand Down Expand Up @@ -117,22 +117,25 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L
samples.add(s, title.string)
return samples.get()

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, onlinejudge.type.Language]:
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]:
"""
:raises NotLoggedInError:
"""

session = session or utils.new_default_session()
# get
resp = utils.request('GET', self.get_url(), session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
select = soup.find('select', attrs={'name': 'programTypeId'})
if select is None:
log.error('not logged in')
return {}
language_dict = {}
raise NotLoggedInError
languages = [] # type: List[Language]
for option in select.findAll('option'):
language_dict[option.attrs['value']] = {'description': option.string}
return language_dict
languages += [Language(option.attrs['value'], option.string)]
return languages

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
"""
:raises NotLoggedInError:
:raises SubmissionError:
Expand All @@ -150,8 +153,8 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
log.debug('form: %s', str(form))
# make data
form = utils.FormSender(form, url=resp.url)
form.set('programTypeId', language)
form.set_file('sourceFile', 'code', code)
form.set('programTypeId', language_id)
form.set_file('sourceFile', filename or 'code', code)
resp = form.request(session=session)
resp.raise_for_status()
# result
Expand Down
12 changes: 6 additions & 6 deletions onlinejudge/service/hackerrank.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LabeledString, LoginError, NotLoggedInError, SubmissionError, TestCase
from onlinejudge.type import *


@utils.singleton
Expand Down Expand Up @@ -189,20 +189,20 @@ def _get_lang_display_mapping(self, session: Optional[requests.Session] = None)
log.debug('lang_display_mapping (parsed): %s', lang_display_mapping)
return lang_display_mapping

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, Dict[str, str]]:
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]:
session = session or utils.new_default_session()
info = self._get_model(session=session)
lang_display_mapping = self._get_lang_display_mapping()
result = {}
result = [] # type: List[Language]
for lang in info['languages']:
descr = lang_display_mapping.get(lang)
if descr is None:
log.warning('display mapping for language `%s\' not found', lang)
descr = lang
result[lang] = {'description': descr}
result += [Language(lang, descr)]
return result

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
"""
:raises NotLoggedInError:
:raises SubmissionError:
Expand All @@ -218,7 +218,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
csrftoken = soup.find('meta', attrs={'name': 'csrf-token'}).attrs['content']
# post
url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}/submissions'.format(self.contest_slug, self.challenge_slug)
payload = {'code': code, 'language': language, 'contest_slug': self.contest_slug}
payload = {'code': code, 'language': str(language_id), 'contest_slug': self.contest_slug}
log.debug('payload: %s', payload)
resp = utils.request('POST', url, session=session, json=payload, headers={'X-CSRF-Token': csrftoken})
# parse
Expand Down
24 changes: 11 additions & 13 deletions onlinejudge/service/topcoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LoginError, NotLoggedInError, SubmissionError
from onlinejudge.type import *


@utils.singleton
Expand Down Expand Up @@ -104,19 +104,18 @@ def from_url(cls, url: str) -> Optional['TopcoderLongContestProblem']:
return cls(**kwargs)
return None

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, Dict[str, str]]:
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]:
session = session or utils.new_default_session()

# at 2017/09/21
return {
'Java': {'value': '1', 'description': 'Java 8'},
'C++': {'value': '3', 'description': 'C++11'},
'C#': {'value': '4', 'description': ''},
'VB': {'value': '5', 'description': ''},
'Python': {'value': '6', 'description': 'Python 2'},
} # yapf: disable
return [
Language(LanguageId('1'), 'Java 8'),
Language(LanguageId('3'), 'C++11'),
Language(LanguageId('4'), 'C#'),
Language(LanguageId('5'), 'VB'),
Language(LanguageId('6'), 'Python 2'),
]

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None, kind: str = 'example') -> onlinejudge.type.Submission:
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None, kind: str = 'example') -> onlinejudge.type.Submission:
"""
:param kind: must be one of `example` (default) or `full`
:raises NotLoggedInError:
Expand Down Expand Up @@ -159,7 +158,6 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses

# post
url = 'https://community.topcoder.com/longcontest/'
language_id = self.get_language_dict(session=session)[language]['value']
data = {
'module': 'Submit',
'rd': self.rd,
Expand All @@ -170,7 +168,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
'example': 'true',
'full': 'false'
}[kind],
'lid': language_id,
'lid': str(language_id),
'code': code,
}
resp = utils.request('POST', url, session=session, data=data)
Expand Down
14 changes: 7 additions & 7 deletions onlinejudge/service/toph.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LoginError, NotLoggedInError, SubmissionError
from onlinejudge.type import *


@utils.singleton
Expand Down Expand Up @@ -108,7 +108,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L
samples.add(s, "Output")
return samples.get()

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, onlinejudge.type.Language]:
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]:
"""
:raises NotImplementedError:
"""
Expand All @@ -120,12 +120,12 @@ def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[
select = soup.find('select', attrs={'name': 'languageId'})
if select is None:
raise NotLoggedInError
language_dict = {}
languages = [] # type: List[Language]
for option in select.findAll('option'):
language_dict[option.attrs['value']] = {'description': option.string.strip()}
return language_dict
languages += [Language(LanguageId(option.attrs['value']), option.string.strip())]
return languages

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> Submission:
"""
:raises NotImplementedError:
:raises SubmissionError:
Expand All @@ -146,7 +146,7 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses

# make data
form = utils.FormSender(form, url=resp.url)
form.set('languageId', language)
form.set('languageId', language_id)
form.set_file('source', 'code', code)
resp = form.request(session=session)
resp.raise_for_status()
Expand Down
17 changes: 8 additions & 9 deletions onlinejudge/service/yukicoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import onlinejudge._implementation.logging as log
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import LabeledString, LoginError, NotLoggedInError, SubmissionError, TestCase
from onlinejudge.type import *


@utils.singleton
Expand Down Expand Up @@ -325,7 +324,7 @@ def _parse_sample_tag(self, tag: bs4.Tag) -> Optional[Tuple[str, str]]:
return utils.textfile(s.lstrip()), pprv.string + ' ' + prv.string
return None

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission:
"""
:raises NotLoggedInError:
"""
Expand All @@ -342,8 +341,8 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
raise NotLoggedInError
# post
form = utils.FormSender(form, url=resp.url)
form.set('lang', language)
form.set_file('file', 'code', code)
form.set('lang', language_id)
form.set_file('file', filename or 'code', code)
form.unset('custom_test')
resp = form.request(session=session)
resp.raise_for_status()
Expand All @@ -360,18 +359,18 @@ def submit_code(self, code: bytes, language: str, session: Optional[requests.Ses
log.warning('yukicoder says: "%s"', div.string)
raise SubmissionError

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, onlinejudge.type.Language]:
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]:
session = session or utils.new_default_session()
# get
# We use the problem page since it is available without logging in
resp = utils.request('GET', self.get_url(), session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
select = soup.find('select', id='lang')
language_dict = {}
languages = [] # type: List[Language]
for option in select.find_all('option'):
language_dict[option.attrs['value']] = {'description': ' '.join(option.string.split())}
return language_dict
languages += [Language(option.attrs['value'], ' '.join(option.string.split()))]
return languages

def get_url(self) -> str:
if self.problem_no:
Expand Down
Loading

0 comments on commit b5af924

Please sign in to comment.