Repository: online-judge-tools/oj

Issue:
oj command fails with `ModuleNotFoundError: No module named 'onlinejudge.__about__'` in version 10.0.0
はじめまして、
今朝、online-judge-toolsをインストールしてojコマンドを使用しようとしましたが、うまくいかなかったので報告します。
インストール手順に従ってインストールを行い、README.mdのFAQも読みました。
私の環境が悪かったり、online-judge-toolsの不具合でなければcloseしてください。
よろしくお願いします。

私の環境は以下のとおりです
macOS Mojave 10.14.6
$ python3 --version
Python 3.8.0
$ pip3 --version
pip 20.1

実行したコマンドは以下のとおりです。
$ pip3 uninstall online-judge-tools #一度アンインストール
Successfully uninstalled online-judge-tools-10.0.0
$ pip3 install --user online-judge-tools
(略)
Successfully installed online-judge-tools-10.0.0
$ oj 
Traceback (most recent call last):
  File "/usr/local/bin/oj", line 5, in <module>
    from onlinejudge_command.main import main
  File "/usr/local/lib/python3.7/site-packages/onlinejudge_command/main.py", line 11, in <module>
    import onlinejudge_command.update_checking as update_checking
  File "/usr/local/lib/python3.7/site-packages/onlinejudge_command/update_checking.py", line 11, in <module>
    import onlinejudge.__about__ as api_version
ModuleNotFoundError: No module named 'onlinejudge.__about__'



Discussion:

Patch:
diff --git a/onlinejudge_command/download_history.py b/onlinejudge_command/download_history.py
index fcf4e79..9bfc989 100644
--- a/onlinejudge_command/download_history.py
+++ b/onlinejudge_command/download_history.py
@@ -7,16 +7,14 @@ from typing import *
 
 import onlinejudge_command.logging as log
 import onlinejudge_command.utils as utils
-
-import onlinejudge
-import onlinejudge.type
+from onlinejudge_workaround_for_conflict.type import Problem
 
 
 class DownloadHistory(object):
     def __init__(self, path: pathlib.Path = utils.user_cache_dir / 'download-history.jsonl'):
         self.path = path
 
-    def add(self, problem: onlinejudge.type.Problem, directory: pathlib.Path = pathlib.Path.cwd()) -> None:
+    def add(self, problem: Problem, directory: pathlib.Path = pathlib.Path.cwd()) -> None:
         self.path.parent.mkdir(parents=True, exist_ok=True)
         with open(str(self.path), 'a') as fh:
             fh.write(json.dumps({
diff --git a/onlinejudge_command/main.py b/onlinejudge_command/main.py
index 3c520e1..f2d359d 100644
--- a/onlinejudge_command/main.py
+++ b/onlinejudge_command/main.py
@@ -10,6 +10,7 @@ import onlinejudge_command.__about__ as version
 import onlinejudge_command.logging as log
 import onlinejudge_command.update_checking as update_checking
 import onlinejudge_command.utils as utils
+import onlinejudge_workaround_for_conflict.__about__ as api_version  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
 from onlinejudge_command.subcommand.download import download
 from onlinejudge_command.subcommand.generate_input import generate_input
 from onlinejudge_command.subcommand.generate_output import generate_output
@@ -18,8 +19,6 @@ from onlinejudge_command.subcommand.submit import submit
 from onlinejudge_command.subcommand.test import test
 from onlinejudge_command.subcommand.test_reactive import test_reactive
 
-import onlinejudge.__about__ as api_version
-
 
 def get_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser(description='Tools for online judge services')
diff --git a/onlinejudge_command/subcommand/download.py b/onlinejudge_command/subcommand/download.py
index 94d2271..c6a66f0 100644
--- a/onlinejudge_command/subcommand/download.py
+++ b/onlinejudge_command/subcommand/download.py
@@ -8,17 +8,16 @@ import onlinejudge_command.download_history
 import onlinejudge_command.format_utils as format_utils
 import onlinejudge_command.logging as log
 import onlinejudge_command.utils as utils
+import onlinejudge_workaround_for_conflict.dispatch as dispatch  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
 import requests.exceptions
-
-import onlinejudge
-import onlinejudge.type
-from onlinejudge.service.yukicoder import YukicoderProblem
+from onlinejudge_workaround_for_conflict.service.yukicoder import YukicoderProblem  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
+from onlinejudge_workaround_for_conflict.type import SampleParseError, TestCase  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
 
 if TYPE_CHECKING:
     import argparse
 
 
-def convert_sample_to_dict(sample: onlinejudge.type.TestCase) -> Dict[str, str]:
+def convert_sample_to_dict(sample: TestCase) -> Dict[str, str]:
     data = {}  # type: Dict[str, str]
     data["name"] = sample.name
     data["input"] = sample.input_data.decode()
@@ -29,7 +28,7 @@ def convert_sample_to_dict(sample: onlinejudge.type.TestCase) -> Dict[str, str]:
 
 def download(args: 'argparse.Namespace') -> None:
     # prepare values
-    problem = onlinejudge.dispatch.problem_from_url(args.url)
+    problem = dispatch.problem_from_url(args.url)
     if problem is None:
         raise requests.exceptions.InvalidURL('The contest "%s" is not supported' % args.url)
     is_default_format = args.format is None and args.directory is None  # must be here since args.directory and args.format are overwritten
@@ -48,7 +47,7 @@ def download(args: 'argparse.Namespace') -> None:
             samples = problem.download_sample_cases(session=sess)
 
     if not samples:
-        raise onlinejudge.type.SampleParseError("Sample not found")
+        raise SampleParseError("Sample not found")
 
     # append the history for submit command
     if not args.dry_run and is_default_format:
@@ -56,7 +55,7 @@ def download(args: 'argparse.Namespace') -> None:
         history.add(problem)
 
     # prepare files to write
-    def iterate_files_to_write(sample: onlinejudge.type.TestCase, *, i: int) -> Iterator[Tuple[str, pathlib.Path, bytes]]:
+    def iterate_files_to_write(sample: TestCase, *, i: int) -> Iterator[Tuple[str, pathlib.Path, bytes]]:
         for ext in ['in', 'out']:
             data = getattr(sample, ext + 'put_data')
             if data is None:
diff --git a/onlinejudge_command/subcommand/login.py b/onlinejudge_command/subcommand/login.py
index e18f997..0bdf3ae 100644
--- a/onlinejudge_command/subcommand/login.py
+++ b/onlinejudge_command/subcommand/login.py
@@ -8,15 +8,15 @@ from typing import *
 
 import onlinejudge_command.logging as log
 import onlinejudge_command.utils as utils
+import onlinejudge_workaround_for_conflict.dispatch as dispatch  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
 import requests
-
-import onlinejudge
+from onlinejudge_workaround_for_conflict.type import LoginError, Service  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
 
 if TYPE_CHECKING:
     import argparse
 
 
-def login_with_password(service: onlinejudge.type.Service, *, username: Optional[str], password: Optional[str], session: requests.Session) -> None:
+def login_with_password(service: Service, *, username: Optional[str], password: Optional[str], session: requests.Session) -> None:
     def get_credentials() -> Tuple[str, str]:
         nonlocal username, password
         if username is None:
@@ -33,7 +33,7 @@ class WebDriverException(Exception):
     pass
 
 
-def login_with_browser(service: onlinejudge.type.Service, *, session: requests.Session) -> None:
+def login_with_browser(service: Service, *, session: requests.Session) -> None:
     try:
         import selenium.webdriver
     except ImportError:
@@ -72,7 +72,7 @@ def login_with_browser(service: onlinejudge.type.Service, *, session: requests.S
         session.cookies.set_cookie(cookie)  # type: ignore
 
 
-def is_logged_in_with_message(service: onlinejudge.type.Service, *, session: requests.Session) -> bool:
+def is_logged_in_with_message(service: Service, *, session: requests.Session) -> bool:
     if service.is_logged_in(session=session):
         log.info('You have already signed in.')
         return True
@@ -82,7 +82,7 @@ def is_logged_in_with_message(service: onlinejudge.type.Service, *, session: req
 
 
 def login(args: 'argparse.Namespace') -> None:
-    service = onlinejudge.dispatch.service_from_url(args.url)
+    service = dispatch.service_from_url(args.url)
     if service is None:
         sys.exit(1)
 
@@ -117,7 +117,7 @@ def login(args: 'argparse.Namespace') -> None:
             except NotImplementedError as e:
                 log.error('%s', e)
                 pass
-            except onlinejudge.type.LoginError:
+            except LoginError:
                 sys.exit(1)
             else:
                 if is_logged_in_with_message(service, session=session):
diff --git a/onlinejudge_command/subcommand/submit.py b/onlinejudge_command/subcommand/submit.py
index 2979487..3a18bbe 100644
--- a/onlinejudge_command/subcommand/submit.py
+++ b/onlinejudge_command/subcommand/submit.py
@@ -9,9 +9,8 @@ from typing import *
 import onlinejudge_command.download_history
 import onlinejudge_command.logging as log
 import onlinejudge_command.utils as utils
-
-import onlinejudge
-from onlinejudge.type import *
+import onlinejudge_workaround_for_conflict.dispatch as dispatch  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
+from onlinejudge_workaround_for_conflict.type import *  # see https://github.com/online-judge-tools/oj/issues/755#issuecomment-623118672
 
 if TYPE_CHECKING:
     import argparse
@@ -35,7 +34,7 @@ def submit(args: 'argparse.Namespace') -> None:
             sys.exit(1)
 
     # parse url
-    problem = onlinejudge.dispatch.problem_from_url(args.url)
+    problem = dispatch.problem_from_url(args.url)
     if problem is None:
         sys.exit(1)
 
diff --git a/onlinejudge_command/update_checking.py b/onlinejudge_command/update_checking.py
index 56d1dce..509adca 100644
--- a/onlinejudge_command/update_checking.py
+++ b/onlinejudge_command/update_checking.py
@@ -6,10 +6,9 @@ from typing import *
 
 import onlinejudge_command.__about__ as version
 import onlinejudge_command.logging as log
+import onlinejudge_workaround_for_conflict.__about__ as api_version
 import requests
-
-import onlinejudge.__about__ as api_version
-from onlinejudge.utils import user_cache_dir
+from onlinejudge_workaround_for_conflict.utils import user_cache_dir
 
 
 def describe_status_code(status_code: int) -> str:
diff --git a/onlinejudge_command/utils.py b/onlinejudge_command/utils.py
index b327da8..de82639 100644
--- a/onlinejudge_command/utils.py
+++ b/onlinejudge_command/utils.py
@@ -16,14 +16,13 @@ from typing.io import *
 
 import onlinejudge_command.__about__ as version
 import onlinejudge_command.logging as log
+import onlinejudge_workaround_for_conflict.utils as utils
 import requests
+from onlinejudge_workaround_for_conflict.type import *
 
-import onlinejudge.utils
-from onlinejudge.type import *
-
-user_data_dir = onlinejudge.utils.user_data_dir
-user_cache_dir = onlinejudge.utils.user_cache_dir
-default_cookie_path = onlinejudge.utils.default_cookie_path
+user_data_dir = utils.user_data_dir
+user_cache_dir = utils.user_cache_dir
+default_cookie_path = utils.default_cookie_path
 
 
 @contextlib.contextmanager
@@ -31,7 +30,7 @@ def new_session_with_our_user_agent(*, path: pathlib.Path) -> Iterator[requests.
     session = requests.Session()
     session.headers['User-Agent'] = '{}/{} (+{})'.format(version.__package_name__, version.__version__, version.__url__)
     log.debug('User-Agent: %s', session.headers['User-Agent'])
-    with onlinejudge.utils.with_cookiejar(session, path=path) as session:
+    with utils.with_cookiejar(session, path=path) as session:
         yield session
 
 
diff --git a/setup.py b/setup.py
index bb265e9..785f57b 100755
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ setup(
     description=version.__description__,
     python_requires='>=3.5',
     install_requires=[
-        'online-judge-api-client >= 10, < 11',
+        'online-judge-api-client >= 10.0.3, < 11',
         'colorama >= 0.3',
         'diff-match-patch >= 20181111',
         'requests >= 2',


Test patch:
diff --git a/tests/command_download_atcoder.py b/tests/command_download_atcoder.py
index 31f1de9..557c528 100644
--- a/tests/command_download_atcoder.py
+++ b/tests/command_download_atcoder.py
@@ -2,8 +2,7 @@ import unittest
 
 import requests.exceptions
 import tests.command_download
-
-from onlinejudge.type import SampleParseError
+from onlinejudge_workaround_for_conflict.type import SampleParseError
 
 
 class DownloadAtCoderTest(unittest.TestCase):
diff --git a/tests/command_download_invalid.py b/tests/command_download_invalid.py
index d0f7513..a59b081 100644
--- a/tests/command_download_invalid.py
+++ b/tests/command_download_invalid.py
@@ -2,8 +2,7 @@ import unittest
 
 import requests.exceptions
 import tests.command_download
-
-from onlinejudge.type import SampleParseError
+from onlinejudge_workaround_for_conflict.type import SampleParseError
 
 
 class DownloadInvalidTest(unittest.TestCase):
diff --git a/tests/command_download_yukicoder.py b/tests/command_download_yukicoder.py
index 21c026a..3c808e7 100644
--- a/tests/command_download_yukicoder.py
+++ b/tests/command_download_yukicoder.py
@@ -1,8 +1,7 @@
 import unittest
 
 import tests.command_download
-
-from onlinejudge.service.yukicoder import YukicoderService
+from onlinejudge_workaround_for_conflict.service.yukicoder import YukicoderService
 
 
 class DownloadYukicoderTest(unittest.TestCase):
diff --git a/tests/command_login.py b/tests/command_login.py
index 1fce547..0cc53a5 100644
--- a/tests/command_login.py
+++ b/tests/command_login.py
@@ -1,12 +1,11 @@
 import unittest
 
 import tests.utils
-
-from onlinejudge.service.atcoder import AtCoderService
-from onlinejudge.service.codeforces import CodeforcesService
-from onlinejudge.service.hackerrank import HackerRankService
-from onlinejudge.service.toph import TophService
-from onlinejudge.service.yukicoder import YukicoderService
+from onlinejudge_workaround_for_conflict.service.atcoder import AtCoderService
+from onlinejudge_workaround_for_conflict.service.codeforces import CodeforcesService
+from onlinejudge_workaround_for_conflict.service.hackerrank import HackerRankService
+from onlinejudge_workaround_for_conflict.service.toph import TophService
+from onlinejudge_workaround_for_conflict.service.yukicoder import YukicoderService
 
 
 class LoginTest(unittest.TestCase):
diff --git a/tests/command_submit.py b/tests/command_submit.py
index 98dcf89..fa3964c 100644
--- a/tests/command_submit.py
+++ b/tests/command_submit.py
@@ -6,12 +6,11 @@ import requests.exceptions
 import tests.utils
 from onlinejudge_command.main import get_parser
 from onlinejudge_command.subcommand.submit import submit
-
-from onlinejudge.service.atcoder import AtCoderService
-from onlinejudge.service.codeforces import CodeforcesService
-from onlinejudge.service.hackerrank import HackerRankService
-from onlinejudge.service.toph import TophService
-from onlinejudge.service.yukicoder import YukicoderService
+from onlinejudge_workaround_for_conflict.service.atcoder import AtCoderService
+from onlinejudge_workaround_for_conflict.service.codeforces import CodeforcesService
+from onlinejudge_workaround_for_conflict.service.hackerrank import HackerRankService
+from onlinejudge_workaround_for_conflict.service.toph import TophService
+from onlinejudge_workaround_for_conflict.service.yukicoder import YukicoderService
 
 
 class SubmitArgumentsTest(unittest.TestCase):
diff --git a/tests/implementation_language_guessing.py b/tests/implementation_language_guessing.py
index 3d27fd7..b158c4c 100644
--- a/tests/implementation_language_guessing.py
+++ b/tests/implementation_language_guessing.py
@@ -3,8 +3,7 @@ import textwrap
 import unittest
 
 import onlinejudge_command.subcommand.submit as submit
-
-from onlinejudge.type import Language, LanguageId
+from onlinejudge_workaround_for_conflict.type import Language, LanguageId
 
 # https://atcoder.jp/contests/language-test-ver1
 languages_atcoder_3 = [