# Python Version: 3.x
import datetime
from abc import ABC, abstractmethod
from typing import Callable, Iterator, List, NamedTuple, NewType, Optional, Tuple
import requests
CredentialsProvider = Callable[[], Tuple[str, str]]
[docs]class LoginError(RuntimeError):
pass
[docs]class Service(ABC):
[docs] def login(self, *, get_credentials: CredentialsProvider, session: Optional[requests.Session] = None) -> None:
"""
:param get_credentials: returns a tuple of (username, password)
:raises LoginError:
"""
raise NotImplementedError
[docs] def get_url_of_login_page(self) -> str:
raise NotImplementedError
[docs] def is_logged_in(self, *, session: Optional[requests.Session] = None) -> bool:
raise NotImplementedError
[docs] @abstractmethod
def get_url(self) -> str:
raise NotImplementedError
[docs] @abstractmethod
def get_name(self) -> str:
"""
example:
- `AtCoder`
- `Codeforces`
- `PKU JudgeOnline`
:note: If you want something like identifier (e.g. `atcoder`, `codeforces` or `poj`), you can use a domain obtained from :py:meth:`get_url`.
"""
raise NotImplementedError
def __repr__(self) -> str:
return '{}.from_url({})'.format(self.__class__.__name__, repr(self.get_url()))
def __eq__(self, other) -> bool:
return self.__class__ == other.__class__ and self.get_url() == other.get_url()
[docs] @classmethod
@abstractmethod
def from_url(self, s: str) -> Optional['Service']:
pass
[docs] def iterate_contests(self, *, session: Optional[requests.Session] = None) -> Iterator['Contest']:
raise NotImplementedError
TestCase = NamedTuple('TestCase', [
('name', str),
('input_name', str),
('input_data', bytes),
('output_name', str),
('output_data', bytes),
])
LanguageId = NewType('LanguageId', str)
"""
:note: This is just a :py:class:`NewType` -ed :py:class:`str` not, but you should not use this other than a label.
"""
Language = NamedTuple('Language', [
('id', LanguageId),
('name', str),
])
"""
:ivar id: :py:class:`LanguageId`
:ivar name: :py:class:`str`
"""
[docs]class NotLoggedInError(RuntimeError):
pass
[docs]class SampleParseError(RuntimeError):
pass
[docs]class SubmissionError(RuntimeError):
pass
[docs]class DownloadedData(ABC):
"""
:note: :py:class:`DownloadedData` and its subclasses represent contents which are obtained by network access. The values may depends your session.
:py:class:`DownloadedData` とそのサブクラスは、ネットワークアクセスの結果得られるようなデータを表現します。その値はログイン状況などにより接続のたびに変化することがあります。
"""
@property
@abstractmethod
def url(self) -> str:
raise NotImplementedError
@property
def json(self) -> Optional[bytes]:
return None
@property
def html(self) -> Optional[bytes]:
return None
@property
def timestamp(self) -> Optional[datetime.datetime]:
return None
@property
def session(self) -> Optional[requests.Session]:
return None
@property
def response(self) -> Optional[requests.Response]:
return None
[docs]class ContestData(DownloadedData):
[docs] def url(self) -> str:
return self.contest.get_url()
@property
@abstractmethod
def contest(self) -> 'Contest':
raise NotImplementedError
@property
def service(self) -> Service:
return self.contest.get_service()
@property
@abstractmethod
def name(self) -> str:
raise NotImplementedError
[docs]class Contest(ABC):
"""
:note: :py:class:`Contest` represents just a URL of a contest, without the data of the contest.
"""
[docs] def list_problems(self, *, session: Optional[requests.Session] = None) -> List['Problem']:
raise NotImplementedError
[docs] def download_data(self, *, session: Optional[requests.Session] = None) -> ContestData:
raise NotImplementedError
[docs] def iterate_submissions(self, *, session: Optional[requests.Session] = None) -> Iterator['Submission']:
raise NotImplementedError
[docs] @abstractmethod
def get_url(self) -> str:
raise NotImplementedError
[docs] @abstractmethod
def get_service(self) -> Service:
raise NotImplementedError
[docs] @classmethod
@abstractmethod
def from_url(self, s: str) -> Optional['Contest']:
pass
[docs]class ProblemData(DownloadedData):
[docs] def url(self) -> str:
return self.problem.get_url()
@property
@abstractmethod
def problem(self) -> 'Problem':
raise NotImplementedError
@property
def contest(self) -> Contest:
return self.problem.get_contest()
@property
def service(self) -> Service:
return self.problem.get_service()
@property
@abstractmethod
def name(self) -> str:
"""
for example of :py:class:`Problem`:
- `器物損壊!高橋君`
- `AtCoDeerくんと変なじゃんけん / AtCoDeer and Rock-Paper`
- `Xor Sum`
"""
raise NotImplementedError
@property
def sample_cases(self) -> Optional[List[TestCase]]:
raise NotImplementedError
[docs]class Problem(ABC):
"""
:note: :py:class:`Problem` represents just a URL of a problem, without the data of the problem.
:py:class:`Problem` はちょうど問題の URL のみを表現します。キャッシュや内部状態は持ちません。
"""
[docs] @abstractmethod
def download_sample_cases(self, *, session: Optional[requests.Session] = None) -> List[TestCase]:
"""
:raises SampleParseError:
"""
raise NotImplementedError
[docs] def download_system_cases(self, *, session: Optional[requests.Session] = None) -> List[TestCase]:
"""
:raises NotLoggedInError:
"""
raise NotImplementedError
[docs] def submit_code(self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> 'Submission':
"""
:param code:
:arg language_id: :py:class:`LanguageId`
:raises NotLoggedInError:
:raises SubmissionError:
"""
raise NotImplementedError
[docs] def get_available_languages(self, *, session: Optional[requests.Session] = None) -> List[Language]:
raise NotImplementedError
[docs] @abstractmethod
def get_url(self) -> str:
raise NotImplementedError
[docs] def get_contest(self) -> Contest:
raise NotImplementedError
[docs] @abstractmethod
def get_service(self) -> Service:
raise NotImplementedError
[docs] def download_data(self, *, session: Optional[requests.Session] = None) -> ProblemData:
raise NotImplementedError
def __repr__(self) -> str:
return '{}.from_url({})'.format(self.__class__.__name__, repr(self.get_url()))
def __eq__(self, other) -> bool:
return self.__class__ == other.__class__ and self.get_url() == other.get_url()
[docs] @classmethod
@abstractmethod
def from_url(self, s: str) -> Optional['Problem']:
pass
[docs]class SubmissionData(DownloadedData):
[docs] def url(self) -> str:
return self.submission.get_url()
@property
@abstractmethod
def submission(self) -> 'Submission':
raise NotImplementedError
@property
def problem(self) -> Problem:
return self.submission.get_problem()
@property
def contest(self) -> Contest:
return self.submission.get_contest()
@property
def service(self) -> Service:
return self.submission.get_service()
@property
def source_code(self) -> bytes:
raise NotImplementedError
@property
@abstractmethod
def status(self) -> str:
raise NotImplementedError
[docs]class Submission(ABC):
[docs] def download_data(self, *, session: Optional[requests.Session] = None) -> SubmissionData:
raise NotImplementedError
[docs] @abstractmethod
def get_url(self) -> str:
raise NotImplementedError
[docs] @abstractmethod
def get_problem(self) -> Problem:
raise NotImplementedError
[docs] def get_contest(self) -> Contest:
return self.get_problem().get_contest()
[docs] @abstractmethod
def get_service(self) -> Service:
return self.get_problem().get_service()
def __repr__(self) -> str:
return '{}.from_url({})'.format(self.__class__.__name__, repr(self.get_url()))
def __eq__(self, other) -> bool:
return self.__class__ == other.__class__ and self.get_url() == other.get_url()
[docs] @classmethod
@abstractmethod
def from_url(cls, s: str) -> Optional['Submission']:
pass