From 51464a5c26ebd646f65aadce9abdf121407885c0 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 07:43:56 +0530 Subject: [PATCH 01/44] chore: set python_requires in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 583f8f4..f7e7f3c 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ license='MIT', keywords='codeforces', url='https://github.com/Mukundan314/python-codeforces', + python_requires='>=3.6,<4', install_requires=['bs4'], extras_requires={ 'docs': ['sphinx', 'sphinx_rtd_theme'] }, packages=find_packages(exclude=['docs']) From 18f90142da89215af07fe06bd4123b7f8a3e4dcf Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 07:48:12 +0530 Subject: [PATCH 02/44] chore: Add classifiers in setup.py --- setup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f7e7f3c..e9880e6 100644 --- a/setup.py +++ b/setup.py @@ -18,5 +18,13 @@ python_requires='>=3.6,<4', install_requires=['bs4'], extras_requires={ 'docs': ['sphinx', 'sphinx_rtd_theme'] }, - packages=find_packages(exclude=['docs']) + packages=find_packages(exclude=['docs']), + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7' + ] ) From 6301a66a1961eb5be7246608902090603faa8cae Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 08:50:00 +0530 Subject: [PATCH 03/44] fix(api): Fix minor bug in api.call method --- codeforces/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeforces/api.py b/codeforces/api.py index 480ce8c..8254bc9 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -70,11 +70,11 @@ def call(method, key=None, secret=None, **kwargs): } elif err.code in (429, 503): time.sleep(1) - return call(method, key, secret, **kargs) + return call(method, key, secret, **kwargs) else: raise if data['status'] == 'FAILED': - raise error.CodeforcesAPIError(data['comment'], method, kargs) + raise error.CodeforcesAPIError(data['comment'], method, kwargs) return data['result'] From 954f54f33d7fcf66a85b3cd7402d38166bf526f1 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 08:52:01 +0530 Subject: [PATCH 04/44] chore(version): bump version (patch) --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8e9a767..32bdcec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '0.2' # The full version, including alpha/beta/rc tags -release = '0.2.0' +release = '0.2.1' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index e9880e6..2205ede 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='python-codeforces', - version='0.2.0', + version='0.2.1', author='Mukundan', author_email='mukundan314@gmail.com', description='Codeforces API wrapper for python', From 4df88ec0881b726273535502b42ef003e5399542 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 11:13:53 +0530 Subject: [PATCH 05/44] chore: Add project urls in setup.py --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2205ede..ff84ae0 100644 --- a/setup.py +++ b/setup.py @@ -26,5 +26,10 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7' - ] + ], + project_urls={ + "Bug Tracker": "https://github.com/Mukundan314/python-codeforces/issues/", + "Documentation": "https://python-codeforces.readthedocs.io/en/stable/", + "Source": "https://github.com/Mukundan314/python-codeforces" + } ) From a455307516f6480ebed154531b152d5403035c8a Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 11:21:15 +0530 Subject: [PATCH 06/44] chore: Update README --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 31a28c9..c660a41 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,35 @@ [![Documentation Status](https://readthedocs.org/projects/python-codeforces/badge/?version=latest)](https://python-codeforces.readthedocs.io/en/latest/?badge=latest) Codeforces API wrapper for python + +## Installation + +#### Using `pip` + +```shell +pip install python-codeforces +``` + +#### From source + +```shell +git clone https://github.com/Mukundan314/python-codeforces.git +cd python-codeforces +pip install . +``` + +#### For Development + +```shell +git clone https://github.com/Mukundan314/python-codeforces.git +cd python-codeforces +pip install -e . +``` + +## Documentation + +Documentation can be found at https://python-codeforces.readthedocs.io/en/latest/ + +## License + +See [LICENSE](LICENSE). From 07b8a971f5235b072e04c7c2713ce97673957996 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 18:00:59 +0530 Subject: [PATCH 07/44] feat(bin): Add script to run program against sample testcases --- bin/cf-run | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 77 insertions(+) create mode 100755 bin/cf-run diff --git a/bin/cf-run b/bin/cf-run new file mode 100755 index 0000000..ab983e7 --- /dev/null +++ b/bin/cf-run @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import argparse +import time +import subprocess + +import codeforces + +parser = argparse.ArgumentParser() + +parser.add_argument( + 'contestId', + type=int, + help=("Id of the contest. It is not the round number. It can be seen in " + "contest URL.") +) + +parser.add_argument( + 'index', + type=str, + help=("A letter or a letter followed by a digit, that represent a problem " + "index in a contest.") +) + +parser.add_argument( + 'program', + type=str, + help="Path to executable that needs to be tested" +) + +parser.add_argument( + '-t', + '--timeout', + type=int, + default=10, + help="Timeout for program in seconds, -1 for no time limit (default: 10)" +) + +args = parser.parse_args() +args.timeout = None if args.timeout == -1 else args.timeout + +title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info( + args.contestId, + args.index +) + +print(title) +print("time limit per test:", time_limit) +print("memory limit per test:", memory_limit) + +print() + +for inp, ans in sample_tests: + start = time.time() + + out = subprocess.run( + args.program, + input=inp.encode('utf-8'), + capture_output=True, + timeout=args.timeout + ).stdout.decode('utf-8') + + time_used = time.time() - start + + print('-' * 80, '\n') + + print('Time: %d ms\n' % (time_used * 1000)) + + print('Input') + print(inp) + + print("Participant's output") + print(out) + + print("Jury's answer") + print(ans) diff --git a/setup.py b/setup.py index ff84ae0..ce74779 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ license='MIT', keywords='codeforces', url='https://github.com/Mukundan314/python-codeforces', + scripts=['bin/cf-run'], python_requires='>=3.6,<4', install_requires=['bs4'], extras_requires={ 'docs': ['sphinx', 'sphinx_rtd_theme'] }, From 4622eef44b030725bca1ef6da2492a0d1853fda7 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 18:08:13 +0530 Subject: [PATCH 08/44] chore(readme): Add instructions for using cf-run --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index c660a41..c30ad81 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,50 @@ cd python-codeforces pip install -e . ``` +## Using `cf-run` + +```shell +cf-run [contestId] [index] [program] +``` + +Example: + +```shell +$ gcc a.c -o ./out +$ cf-run 1100 A ./out +A. Roman and Browser +time limit per test: 1 second +memory limit per test: 256 megabytes + +-------------------------------------------------------------------------------- + +Time: 16 ms + +Input +4 2 +1 1 -1 1 + +Participant's output +2 + +Jury's answer +2 + +-------------------------------------------------------------------------------- + +Time: 19 ms + +Input +14 3 +-1 1 -1 -1 1 -1 -1 1 -1 -1 1 -1 -1 1 + +Participant's output +9 + +Jury's answer +9 +``` + ## Documentation Documentation can be found at https://python-codeforces.readthedocs.io/en/latest/ From 78af61a6471e6c5a76595bc1a5468f9bc2a631cb Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Wed, 16 Jan 2019 20:56:19 +0530 Subject: [PATCH 09/44] chore(bin): use colorama to print in bold --- bin/cf-run | 9 ++++++--- setup.py | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bin/cf-run b/bin/cf-run index ab983e7..f35e122 100755 --- a/bin/cf-run +++ b/bin/cf-run @@ -4,8 +4,11 @@ import argparse import time import subprocess +import colorama import codeforces +colorama.init(autoreset=True) + parser = argparse.ArgumentParser() parser.add_argument( @@ -66,11 +69,11 @@ for inp, ans in sample_tests: print('Time: %d ms\n' % (time_used * 1000)) - print('Input') + print(colorama.Style.BRIGHT + 'Input') print(inp) - print("Participant's output") + print(colorama.Style.BRIGHT + "Participant's output") print(out) - print("Jury's answer") + print(colorama.Style.BRIGHT + "Jury's answer") print(ans) diff --git a/setup.py b/setup.py index ce74779..eefe6d2 100644 --- a/setup.py +++ b/setup.py @@ -7,19 +7,9 @@ setup( name='python-codeforces', version='0.2.1', - author='Mukundan', - author_email='mukundan314@gmail.com', description='Codeforces API wrapper for python', long_description=long_description, long_description_content_type='text/markdown', - license='MIT', - keywords='codeforces', - url='https://github.com/Mukundan314/python-codeforces', - scripts=['bin/cf-run'], - python_requires='>=3.6,<4', - install_requires=['bs4'], - extras_requires={ 'docs': ['sphinx', 'sphinx_rtd_theme'] }, - packages=find_packages(exclude=['docs']), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -28,6 +18,16 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7' ], + keywords='codeforces', + url='https://github.com/Mukundan314/python-codeforces', + author='Mukundan', + author_email='mukundan314@gmail.com', + license='MIT', + packages=find_packages(exclude=['docs']), + install_requires=['bs4', 'colorama'], + extras_requires={ 'docs': ['sphinx', 'sphinx_rtd_theme'] }, + scripts=['bin/cf-run'], + python_requires='>=3.6,<4', project_urls={ "Bug Tracker": "https://github.com/Mukundan314/python-codeforces/issues/", "Documentation": "https://python-codeforces.readthedocs.io/en/stable/", From 2de015de547ec562a90e6360c8a00e5bce9adae3 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 07:53:08 +0530 Subject: [PATCH 10/44] fix(problem): Fix code to get testcases in get_info --- codeforces/problem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeforces/problem.py b/codeforces/problem.py index d3fc9cf..f0fa09b 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -47,8 +47,8 @@ def get_info(contest_id, index, lang='en'): time_limit = soup.find_all("div", class_="time-limit")[0].text[19:] memory_limit = soup.find_all("div", class_="memory-limit")[0].text[21:] - i = [i.pre.string[1:] for i in soup.find_all("div", class_="input")] - o = [i.pre.string[1:] for i in soup.find_all("div", class_="output")] + i = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="input")] + o = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="output")] sample_tests = zip(i, o) From ab5700092017e94b73bcdf21411e3a1164e2b198 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 07:55:22 +0530 Subject: [PATCH 11/44] test: Add tests with pytest and tox --- test/test_api.py | 28 ++++++++++++++++++++++++++++ test/test_problem.py | 14 ++++++++++++++ tox.ini | 13 +++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 test/test_api.py create mode 100644 test/test_problem.py create mode 100644 tox.ini diff --git a/test/test_api.py b/test/test_api.py new file mode 100644 index 0000000..39e79bb --- /dev/null +++ b/test/test_api.py @@ -0,0 +1,28 @@ +import pytest +import codeforces + +class TestCall(object): + def test_undefined_method(self): + with pytest.raises(codeforces.error.CodeforcesAPIError): + method = "undefined.method" + codeforces.api.call(method) + + def test_unauth_method(self): + method = "user.info" + handles = "python-codeforces" + + codeforces.api.call(method, handles=handles) + + key = "0d905168ea10217dd91472e861bf8c80932f060e" + secret = "3d3872085b0255159381a1884e9f66d5213ba796" + codeforces.api.call(method, key=key, secret=secret, handles=handles) + + def test_auth_method(self): + method = "user.friends" + + key = "0d905168ea10217dd91472e861bf8c80932f060e" + secret = "3d3872085b0255159381a1884e9f66d5213ba796" + codeforces.api.call(method, key=key, secret=secret) + + with pytest.raises(codeforces.error.CodeforcesAPIError): + codeforces.api.call(method) diff --git a/test/test_problem.py b/test/test_problem.py new file mode 100644 index 0000000..8eafcfb --- /dev/null +++ b/test/test_problem.py @@ -0,0 +1,14 @@ +import pytest +import codeforces + +class TestGetInfo(object): + def test_normal_problem(self): + contest_id = 1 + index = 'A' + + title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info(contest_id, index) + + assert(title == 'A. Theatre Square') + assert(time_limit == '1 second') + assert(memory_limit == '256 megabytes') + assert(list(sample_tests) == [('6 6 4', '4')]) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..665b419 --- /dev/null +++ b/tox.ini @@ -0,0 +1,13 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py36,py37 + +[testenv] +deps = + pytest +commands = + pytest From fb3d15269789cc16754355bddff4763fdd07c0e0 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 08:04:56 +0530 Subject: [PATCH 12/44] chore(version): bump version (patch) --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 32bdcec..d04f552 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '0.2' # The full version, including alpha/beta/rc tags -release = '0.2.1' +release = '0.2.2' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index eefe6d2..bde6f2a 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='python-codeforces', - version='0.2.1', + version='0.2.2', description='Codeforces API wrapper for python', long_description=long_description, long_description_content_type='text/markdown', From 055ac4244504211f50290e578035be81966f0ca9 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 12:09:19 +0530 Subject: [PATCH 13/44] style: sort imports --- bin/cf-run | 3 ++- codeforces/problem.py | 3 ++- setup.py | 3 +-- test/test_api.py | 3 ++- test/test_problem.py | 3 ++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bin/cf-run b/bin/cf-run index f35e122..3c031e5 100755 --- a/bin/cf-run +++ b/bin/cf-run @@ -1,10 +1,11 @@ #!/usr/bin/env python import argparse -import time import subprocess +import time import colorama + import codeforces colorama.init(autoreset=True) diff --git a/codeforces/problem.py b/codeforces/problem.py index f0fa09b..af974de 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -1,6 +1,7 @@ import os -import urllib.request import urllib.error +import urllib.request + from bs4 import BeautifulSoup __all__ = ['get_info'] diff --git a/setup.py b/setup.py index bde6f2a..1388361 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ -from setuptools import setup, find_packages - +from setuptools import find_packages, setup with open('README.md') as f: long_description = f.read() diff --git a/test/test_api.py b/test/test_api.py index 39e79bb..fd89c1d 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -1,5 +1,6 @@ -import pytest import codeforces +import pytest + class TestCall(object): def test_undefined_method(self): diff --git a/test/test_problem.py b/test/test_problem.py index 8eafcfb..a5b1c44 100644 --- a/test/test_problem.py +++ b/test/test_problem.py @@ -1,5 +1,6 @@ -import pytest import codeforces +import pytest + class TestGetInfo(object): def test_normal_problem(self): From 047d4df071aac7dfc7d4265388a3c3868a19d27f Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 12:57:40 +0530 Subject: [PATCH 14/44] docs(codeforces.error): Add docstrings to codeforces/error.py --- codeforces/error.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codeforces/error.py b/codeforces/error.py index 0740271..18e3f0e 100644 --- a/codeforces/error.py +++ b/codeforces/error.py @@ -1,6 +1,11 @@ +"""All exceptions used in codeforces.""" + __all__ = ['CodeforcesAPIError'] + class CodeforcesAPIError(Exception): + """Raised by api.call if method returns a error or is not found.""" + def __init__(self, comment, method, args): self.comment = comment self.method = method From 02eac0c8106e5e08fe4abc7be385e3a04f99b850 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 13:00:27 +0530 Subject: [PATCH 15/44] style: lint files with flake8 --- codeforces/__init__.py | 7 ++++++- codeforces/api.py | 11 +++++------ codeforces/problem.py | 3 ++- setup.py | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/codeforces/__init__.py b/codeforces/__init__.py index 0380715..8ebe77b 100644 --- a/codeforces/__init__.py +++ b/codeforces/__init__.py @@ -1,3 +1,8 @@ -from . import error +""" +Codeforces API wrapper for python +""" from . import api +from . import error from . import problem + +__all__ = ['error', 'api', 'problem'] diff --git a/codeforces/api.py b/codeforces/api.py index 8254bc9..c45a255 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -1,6 +1,4 @@ -""" -Functions to call the codeforces api -""" +"""Functions to call the codeforces api.""" import hashlib import json import os @@ -18,7 +16,7 @@ CODEFORCES_API_URL = "https://codeforces.com/api/" -def __generate_api_sig(method, args, secret): +def generate_api_sig(method, args, secret): rand = "%06d" % random.randint(0, 999999) url_args = urllib.parse.urlencode(sorted(args.items())) return rand + hashlib.sha512( @@ -28,7 +26,7 @@ def __generate_api_sig(method, args, secret): def call(method, key=None, secret=None, **kwargs): """ - Call a Codeforces API method + Call a Codeforces API method. Parameters ---------- @@ -46,13 +44,14 @@ def call(method, key=None, secret=None, **kwargs): ------- any A python object containing the results of the api call. + """ args = kwargs.copy() if (key is not None) and (secret is not None): args['time'] = int(time.time()) args['apiKey'] = key - args['apiSig'] = __generate_api_sig(method, args, secret) + args['apiSig'] = generate_api_sig(method, args, secret) url_args = urllib.parse.urlencode(args) url = os.path.join(CODEFORCES_API_URL, "%s?%s" % (method, url_args)) diff --git a/codeforces/problem.py b/codeforces/problem.py index af974de..7da1157 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -1,3 +1,4 @@ +"""Functions to info about a problem.""" import os import urllib.error import urllib.request @@ -33,8 +34,8 @@ def get_info(contest_id, index, lang='en'): Memory limit specification for the problem. sample_tests: zip Sample tests given for the problem. - """ + """ problem_url = os.path.join( CODEFORCES_URL, "contest/%d/problem/%s?lang=%s" % (contest_id, index, lang) diff --git a/setup.py b/setup.py index 1388361..bc139b8 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ license='MIT', packages=find_packages(exclude=['docs']), install_requires=['bs4', 'colorama'], - extras_requires={ 'docs': ['sphinx', 'sphinx_rtd_theme'] }, + extras_requires={'docs': ['sphinx', 'sphinx_rtd_theme']}, scripts=['bin/cf-run'], python_requires='>=3.6,<4', project_urls={ From 0b28c21773e0bb6a8954984daf38af0be5270923 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 14:38:56 +0530 Subject: [PATCH 16/44] style: Lint files with pylint --- codeforces/api.py | 4 ++-- codeforces/problem.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codeforces/api.py b/codeforces/api.py index c45a255..d051053 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -16,7 +16,7 @@ CODEFORCES_API_URL = "https://codeforces.com/api/" -def generate_api_sig(method, args, secret): +def _generate_api_sig(method, args, secret): rand = "%06d" % random.randint(0, 999999) url_args = urllib.parse.urlencode(sorted(args.items())) return rand + hashlib.sha512( @@ -51,7 +51,7 @@ def call(method, key=None, secret=None, **kwargs): if (key is not None) and (secret is not None): args['time'] = int(time.time()) args['apiKey'] = key - args['apiSig'] = generate_api_sig(method, args, secret) + args['apiSig'] = _generate_api_sig(method, args, secret) url_args = urllib.parse.urlencode(args) url = os.path.join(CODEFORCES_API_URL, "%s?%s" % (method, url_args)) diff --git a/codeforces/problem.py b/codeforces/problem.py index 7da1157..768fc32 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -49,9 +49,9 @@ def get_info(contest_id, index, lang='en'): time_limit = soup.find_all("div", class_="time-limit")[0].text[19:] memory_limit = soup.find_all("div", class_="memory-limit")[0].text[21:] - i = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="input")] - o = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="output")] + inputs = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="input")] + outputs = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="output")] - sample_tests = zip(i, o) + sample_tests = zip(inputs, outputs) return (title, time_limit, memory_limit, sample_tests) From 7be54b50472fe996ec9811e2790c316ea6b3ec55 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 16:09:24 +0530 Subject: [PATCH 17/44] test: Add linters in tox.ini --- tox.ini | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 665b419..59a554a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,32 @@ # and then run "tox" from this directory. [tox] -envlist = py36,py37 +envlist = py36, py37, flake8, pylint +skip_missing_interpreters = true [testenv] deps = pytest commands = pytest + +[testenv:flake8] +basepython = python +skip_install = true +deps = + flake8 + flake8-docstrings>=0.2.7 + flake8-import-order>=0.9 + pep8-naming + flake8-colors +commands = + flake8 {toxinidir}/codeforces/ {toxinidir}/test/ {toxinidir}/setup.py + +[testenv:pylint] +basepython = python +skip_install = true +deps = + pylint + -r{toxinidir}/requirements.txt +commands = + pylint {toxinidir}/codeforces/ From 20cd816bd368b24dab682bd44aa93ad0b1853e00 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Thu, 17 Jan 2019 18:32:03 +0530 Subject: [PATCH 18/44] test(travis): Add travis-ci integration --- .travis.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9e26ac2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: python + +sudo: required +dist: xenial + +matrix: + include: + - python: 3.6 + env: TOXENV=py36 + - python: 3.7 + env: TOXENV=py37 + + - python: 3.7 + env: TOXENV=flake8 + - python: 3.7 + env: TOXENV=pylint + + allow_failures: + - env: TOXENV=flake8 + - env: TOXENV=pylint + +install: pip install tox + +script: tox From af3df2d366895c28bcf91f9077f4d764b8b4402f Mon Sep 17 00:00:00 2001 From: Mukundan Senthil <30190448+Mukundan314@users.noreply.github.com> Date: Thu, 17 Jan 2019 18:41:07 +0530 Subject: [PATCH 19/44] chore: Add travis-ci build badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c30ad81..7967e27 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # python-codeforces [![Documentation Status](https://readthedocs.org/projects/python-codeforces/badge/?version=latest)](https://python-codeforces.readthedocs.io/en/latest/?badge=latest) +[![Build Status](https://travis-ci.com/Mukundan314/python-codeforces.svg?branch=master)](https://travis-ci.com/Mukundan314/python-codeforces) Codeforces API wrapper for python @@ -36,7 +37,7 @@ cf-run [contestId] [index] [program] Example: -```shell +``` $ gcc a.c -o ./out $ cf-run 1100 A ./out A. Roman and Browser From 1079ba3b8d38d8cfc12e801ac22cb2fa67652775 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil <30190448+Mukundan314@users.noreply.github.com> Date: Fri, 18 Jan 2019 06:44:47 +0530 Subject: [PATCH 20/44] chore: Add issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 27 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e942a81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +```python +>>> # short program to reproduce the bug +``` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**System (please complete the following information):** + - OS: [e.g. Windows] + - python-codeforces version [e.g. 0.2.2] + - python version [eg: 3.7] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From adbc3daff69a270fcabe2d893d849b2daec26636 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Fri, 18 Jan 2019 06:55:24 +0530 Subject: [PATCH 21/44] chore: update dependencies --- requirements.txt | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c1f5f71..239ff70 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ beautifulsoup4 +colorama diff --git a/setup.py b/setup.py index bc139b8..c325211 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ author_email='mukundan314@gmail.com', license='MIT', packages=find_packages(exclude=['docs']), - install_requires=['bs4', 'colorama'], + install_requires=['beautifulsoup4', 'colorama'], extras_requires={'docs': ['sphinx', 'sphinx_rtd_theme']}, scripts=['bin/cf-run'], python_requires='>=3.6,<4', From 58288038166c11b8bf9fff60246dce91c2d96f19 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil <30190448+Mukundan314@users.noreply.github.com> Date: Fri, 18 Jan 2019 08:20:29 +0530 Subject: [PATCH 22/44] chore: Add supported python versions badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7967e27..aa768d2 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![Documentation Status](https://readthedocs.org/projects/python-codeforces/badge/?version=latest)](https://python-codeforces.readthedocs.io/en/latest/?badge=latest) [![Build Status](https://travis-ci.com/Mukundan314/python-codeforces.svg?branch=master)](https://travis-ci.com/Mukundan314/python-codeforces) +[![Supported Python +versions](https://img.shields.io/pypi/pyversions/python-codeforces.svg)](https://pypi.org/project/tox/) Codeforces API wrapper for python From 9de27b3ec30f3aecb2269b4120bb0cb8c42037c8 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil <30190448+Mukundan314@users.noreply.github.com> Date: Fri, 18 Jan 2019 08:29:26 +0530 Subject: [PATCH 23/44] chore: Update readme --- README.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index aa768d2..cfa0495 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,27 @@ -# python-codeforces -[![Documentation Status](https://readthedocs.org/projects/python-codeforces/badge/?version=latest)](https://python-codeforces.readthedocs.io/en/latest/?badge=latest) -[![Build Status](https://travis-ci.com/Mukundan314/python-codeforces.svg?branch=master)](https://travis-ci.com/Mukundan314/python-codeforces) -[![Supported Python -versions](https://img.shields.io/pypi/pyversions/python-codeforces.svg)](https://pypi.org/project/tox/) +

python-codeforces

-Codeforces API wrapper for python +
+ Codeforces API wrapper for python +
-## Installation +
+ + + +--- + +### Installation #### Using `pip` @@ -31,7 +45,7 @@ cd python-codeforces pip install -e . ``` -## Using `cf-run` +### Using `cf-run` ```shell cf-run [contestId] [index] [program] @@ -46,7 +60,7 @@ A. Roman and Browser time limit per test: 1 second memory limit per test: 256 megabytes --------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- Time: 16 ms @@ -60,7 +74,7 @@ Participant's output Jury's answer 2 --------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- Time: 19 ms @@ -75,10 +89,10 @@ Jury's answer 9 ``` -## Documentation +### Documentation Documentation can be found at https://python-codeforces.readthedocs.io/en/latest/ -## License +### License See [LICENSE](LICENSE). From 3905f48f16a98bbb37b6c4786f70d36649638bae Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Sat, 19 Jan 2019 07:42:28 +0530 Subject: [PATCH 24/44] feat(problem): Add support for gym problems --- codeforces/problem.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/codeforces/problem.py b/codeforces/problem.py index 768fc32..85ca93e 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -10,7 +10,7 @@ CODEFORCES_URL = "https://codeforces.com" -def get_info(contest_id, index, lang='en'): +def get_info(contest_id, index, gym=False, lang='en'): """ Get info for a contest problem. @@ -23,6 +23,9 @@ def get_info(contest_id, index, lang='en'): Usually a letter of a letter, followed by a digit, that represent a problem index in a contest. It can be seen in problem URL. For example: /contest/566/**A** + gym: bool + If true gym problem is returned otherwise regular problem is + returned. Returns ------- @@ -36,10 +39,16 @@ def get_info(contest_id, index, lang='en'): Sample tests given for the problem. """ - problem_url = os.path.join( - CODEFORCES_URL, - "contest/%d/problem/%s?lang=%s" % (contest_id, index, lang) - ) + if gym: + problem_url = os.path.join( + CODEFORCES_URL, + "gym/%d/problem/%s?lang=%s" % (contest_id, index, lang) + ) + else: + problem_url = os.path.join( + CODEFORCES_URL, + "contest/%d/problem/%s?lang=%s" % (contest_id, index, lang) + ) with urllib.request.urlopen(problem_url) as res: soup = BeautifulSoup(res.read(), 'html.parser') @@ -49,8 +58,13 @@ def get_info(contest_id, index, lang='en'): time_limit = soup.find_all("div", class_="time-limit")[0].text[19:] memory_limit = soup.find_all("div", class_="memory-limit")[0].text[21:] - inputs = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="input")] - outputs = [i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="output")] + inputs = [ + i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="input") + ] + + outputs = [ + i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="output") + ] sample_tests = zip(inputs, outputs) From 062e42f27bfccd467f475509ffc46987b2f79489 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Sat, 19 Jan 2019 19:23:15 +0530 Subject: [PATCH 25/44] feat(cf-run): Add option to run on gym contests --- bin/cf-run | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/cf-run b/bin/cf-run index 3c031e5..205e147 100755 --- a/bin/cf-run +++ b/bin/cf-run @@ -40,12 +40,20 @@ parser.add_argument( help="Timeout for program in seconds, -1 for no time limit (default: 10)" ) +parser.add_argument( + '-g', + '--gym', + action='store_true', + help="If true open gym contest instead of regular contest. (default: false)" +) + args = parser.parse_args() args.timeout = None if args.timeout == -1 else args.timeout title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info( args.contestId, - args.index + args.index, + gym=args.gym ) print(title) From 00b022bd6e0c3b94a7f02a24066058ae7799110b Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Sat, 19 Jan 2019 19:24:09 +0530 Subject: [PATCH 26/44] fix(problem): Fix bug in getting testcases --- codeforces/problem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeforces/problem.py b/codeforces/problem.py index 85ca93e..ba275b6 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -59,11 +59,11 @@ def get_info(contest_id, index, gym=False, lang='en'): memory_limit = soup.find_all("div", class_="memory-limit")[0].text[21:] inputs = [ - i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="input") + i.pre.get_text('\n') for i in soup.find_all("div", class_="input") ] outputs = [ - i.pre.text.lstrip('\n') for i in soup.find_all("div", class_="output") + i.pre.get_text('\n') for i in soup.find_all("div", class_="output") ] sample_tests = zip(inputs, outputs) From c4bdd0bff2e9ad48bbee4c3f4b6864dcc692f7e7 Mon Sep 17 00:00:00 2001 From: mukundan314 Date: Sat, 19 Jan 2019 23:13:58 +0530 Subject: [PATCH 27/44] chore(version): Bump version (patch) --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d04f552..93aabc9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '0.2' # The full version, including alpha/beta/rc tags -release = '0.2.2' +release = '0.2.3' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index c325211..d9a7794 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='python-codeforces', - version='0.2.2', + version='0.2.3', description='Codeforces API wrapper for python', long_description=long_description, long_description_content_type='text/markdown', From 3c12b5d0c1ee67ac22338ee3046cdff7d5196cde Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Sun, 17 Feb 2019 14:00:01 +0530 Subject: [PATCH 28/44] chore: Update usage docs for cf-run --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cfa0495..1a04510 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,22 @@ pip install -e . ### Using `cf-run` ```shell -cf-run [contestId] [index] [program] +usage: cf-run [-h] [-t TIMEOUT] [-g] contestId index program + +positional arguments: + contestId Id of the contest. It is not the round number. It can + be seen in contest URL. + index A letter or a letter followed by a digit, that + represent a problem index in a contest. + program Path to executable that needs to be tested + +optional arguments: + -h, --help show this help message and exit + -t TIMEOUT, --timeout TIMEOUT + Timeout for program in seconds, -1 for no time limit + (default: 10) + -g, --gym If true open gym contest instead of regular contest. + (default: false) ``` Example: From 439dc026121ce71e0a41b3d69d72b2a40e5482ac Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Thu, 21 Feb 2019 21:52:37 +0530 Subject: [PATCH 29/44] feat: Use entry_points instead of scripts --- bin/cf-run | 88 ------------------------------------------ codeforces/__init__.py | 3 +- codeforces/cf_run.py | 75 +++++++++++++++++++++++++++++++++++ setup.py | 4 +- 4 files changed, 80 insertions(+), 90 deletions(-) delete mode 100755 bin/cf-run create mode 100644 codeforces/cf_run.py diff --git a/bin/cf-run b/bin/cf-run deleted file mode 100755 index 205e147..0000000 --- a/bin/cf-run +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python - -import argparse -import subprocess -import time - -import colorama - -import codeforces - -colorama.init(autoreset=True) - -parser = argparse.ArgumentParser() - -parser.add_argument( - 'contestId', - type=int, - help=("Id of the contest. It is not the round number. It can be seen in " - "contest URL.") -) - -parser.add_argument( - 'index', - type=str, - help=("A letter or a letter followed by a digit, that represent a problem " - "index in a contest.") -) - -parser.add_argument( - 'program', - type=str, - help="Path to executable that needs to be tested" -) - -parser.add_argument( - '-t', - '--timeout', - type=int, - default=10, - help="Timeout for program in seconds, -1 for no time limit (default: 10)" -) - -parser.add_argument( - '-g', - '--gym', - action='store_true', - help="If true open gym contest instead of regular contest. (default: false)" -) - -args = parser.parse_args() -args.timeout = None if args.timeout == -1 else args.timeout - -title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info( - args.contestId, - args.index, - gym=args.gym -) - -print(title) -print("time limit per test:", time_limit) -print("memory limit per test:", memory_limit) - -print() - -for inp, ans in sample_tests: - start = time.time() - - out = subprocess.run( - args.program, - input=inp.encode('utf-8'), - capture_output=True, - timeout=args.timeout - ).stdout.decode('utf-8') - - time_used = time.time() - start - - print('-' * 80, '\n') - - print('Time: %d ms\n' % (time_used * 1000)) - - print(colorama.Style.BRIGHT + 'Input') - print(inp) - - print(colorama.Style.BRIGHT + "Participant's output") - print(out) - - print(colorama.Style.BRIGHT + "Jury's answer") - print(ans) diff --git a/codeforces/__init__.py b/codeforces/__init__.py index 8ebe77b..fb83ce6 100644 --- a/codeforces/__init__.py +++ b/codeforces/__init__.py @@ -4,5 +4,6 @@ from . import api from . import error from . import problem +from . import cf_run -__all__ = ['error', 'api', 'problem'] +__all__ = ['error', 'api', 'problem', 'cf_run'] diff --git a/codeforces/cf_run.py b/codeforces/cf_run.py new file mode 100644 index 0000000..46bc8e9 --- /dev/null +++ b/codeforces/cf_run.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +import argparse +import subprocess +import time + +import colorama + +import codeforces + +colorama.init(autoreset=True) + +def main(argv=None): + parser = argparse.ArgumentParser() + + parser.add_argument('contestId', type=int, + help=("Id of the contest. It is not the round number. " + "It can be seen in contest URL.")) + + parser.add_argument('index', type=str, + help=("A letter or a letter followed by a digit, that " + "represent a problem index in a contest.")) + + parser.add_argument('program', type=str, + help="Path to executable that needs to be tested") + + parser.add_argument('-t', '--timeout', type=int, default=10, + help=("Timeout for program in seconds, -1 for no time " + "limit (default: 10)")) + + parser.add_argument('-g', '--gym', action='store_true', + help=("If true open gym contest instead of regular " + "contest. (default: false)")) + + if argv: + args = parser.parse_args(argv) + else: + args = parser.parse_args() + + args.timeout = None if args.timeout == -1 else args.timeout + + title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info( + args.contestId, args.index, gym=args.gym + ) + + print(title) + print("time limit per test:", time_limit) + print("memory limit per test:", memory_limit) + + print() + + for inp, ans in sample_tests: + start = time.time() + + out = subprocess.run( + args.program, + input=inp.encode('utf-8'), + capture_output=True, + timeout=args.timeout + ).stdout.decode('utf-8') + + time_used = time.time() - start + + print('-' * 80, '\n') + + print('Time: %d ms\n' % (time_used * 1000)) + + print(colorama.Style.BRIGHT + 'Input') + print(inp) + + print(colorama.Style.BRIGHT + "Participant's output") + print(out) + + print(colorama.Style.BRIGHT + "Jury's answer") + print(ans) diff --git a/setup.py b/setup.py index d9a7794..1fc0976 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,9 @@ packages=find_packages(exclude=['docs']), install_requires=['beautifulsoup4', 'colorama'], extras_requires={'docs': ['sphinx', 'sphinx_rtd_theme']}, - scripts=['bin/cf-run'], + entry_points={ + 'console_scripts': ['cf-run = codeforces.cf_run:main'] + }, python_requires='>=3.6,<4', project_urls={ "Bug Tracker": "https://github.com/Mukundan314/python-codeforces/issues/", From 93ad15e38d15e81fd4224558a32128773a0afcee Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Thu, 21 Feb 2019 21:53:16 +0530 Subject: [PATCH 30/44] chore(version): Bump version (patch) --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 93aabc9..0a1e4e5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '0.2' # The full version, including alpha/beta/rc tags -release = '0.2.3' +release = '0.2.5' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index 1fc0976..eafbe67 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='python-codeforces', - version='0.2.3', + version='0.2.5', description='Codeforces API wrapper for python', long_description=long_description, long_description_content_type='text/markdown', From 324f7c565844418533ce7f7a94f0102ec96041a3 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 09:40:04 +0530 Subject: [PATCH 31/44] style: Remove trailing whitespaces --- codeforces/cf_run.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/codeforces/cf_run.py b/codeforces/cf_run.py index 46bc8e9..a2497b8 100644 --- a/codeforces/cf_run.py +++ b/codeforces/cf_run.py @@ -12,64 +12,64 @@ def main(argv=None): parser = argparse.ArgumentParser() - + parser.add_argument('contestId', type=int, help=("Id of the contest. It is not the round number. " "It can be seen in contest URL.")) - + parser.add_argument('index', type=str, help=("A letter or a letter followed by a digit, that " "represent a problem index in a contest.")) - + parser.add_argument('program', type=str, help="Path to executable that needs to be tested") - + parser.add_argument('-t', '--timeout', type=int, default=10, help=("Timeout for program in seconds, -1 for no time " "limit (default: 10)")) - + parser.add_argument('-g', '--gym', action='store_true', help=("If true open gym contest instead of regular " "contest. (default: false)")) - + if argv: args = parser.parse_args(argv) else: args = parser.parse_args() args.timeout = None if args.timeout == -1 else args.timeout - + title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info( args.contestId, args.index, gym=args.gym - ) + ) print(title) print("time limit per test:", time_limit) print("memory limit per test:", memory_limit) - + print() - + for inp, ans in sample_tests: start = time.time() - + out = subprocess.run( args.program, input=inp.encode('utf-8'), capture_output=True, timeout=args.timeout ).stdout.decode('utf-8') - + time_used = time.time() - start - + print('-' * 80, '\n') - + print('Time: %d ms\n' % (time_used * 1000)) - + print(colorama.Style.BRIGHT + 'Input') print(inp) - + print(colorama.Style.BRIGHT + "Participant's output") print(out) - + print(colorama.Style.BRIGHT + "Jury's answer") print(ans) From 9f543857411cf181562b00441b3d8b89322454cc Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 09:54:32 +0530 Subject: [PATCH 32/44] chore: remove cyclic import from cf_run.py --- codeforces/cf_run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeforces/cf_run.py b/codeforces/cf_run.py index a2497b8..c92b15d 100644 --- a/codeforces/cf_run.py +++ b/codeforces/cf_run.py @@ -6,7 +6,7 @@ import colorama -import codeforces +from . import problem colorama.init(autoreset=True) @@ -39,7 +39,7 @@ def main(argv=None): args.timeout = None if args.timeout == -1 else args.timeout - title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info( + title, time_limit, memory_limit, sample_tests = problem.get_info( args.contestId, args.index, gym=args.gym ) From 5fcb3d361d385b15b11750478c2ba5316f0a5294 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 09:55:13 +0530 Subject: [PATCH 33/44] fix(security): Use secrets instead of random --- codeforces/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeforces/api.py b/codeforces/api.py index d051053..38b3530 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -2,7 +2,7 @@ import hashlib import json import os -import random +import secrets import time import urllib.error import urllib.parse @@ -17,7 +17,7 @@ def _generate_api_sig(method, args, secret): - rand = "%06d" % random.randint(0, 999999) + rand = "%06d" % secrets.randbelow(999999) url_args = urllib.parse.urlencode(sorted(args.items())) return rand + hashlib.sha512( ("%s/%s?%s#%s" % (rand, method, url_args, secret)).encode('utf-8') From 7d93b53fa8043ae7304087064b9dd1a524e0fa0b Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 17:19:33 +0530 Subject: [PATCH 34/44] chore: Update readme --- README.md | 44 +++++--------------------------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 1a04510..1a46817 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,11 @@ cd python-codeforces pip install -e . ``` -### Using `cf-run` +### Commandline tools + +#### `cf-run` + +Run a program against sample testcases. ```shell usage: cf-run [-h] [-t TIMEOUT] [-g] contestId index program @@ -66,44 +70,6 @@ optional arguments: (default: false) ``` -Example: - -``` -$ gcc a.c -o ./out -$ cf-run 1100 A ./out -A. Roman and Browser -time limit per test: 1 second -memory limit per test: 256 megabytes - --------------------------------------------------------------------------------- - -Time: 16 ms - -Input -4 2 -1 1 -1 1 - -Participant's output -2 - -Jury's answer -2 - --------------------------------------------------------------------------------- - -Time: 19 ms - -Input -14 3 --1 1 -1 -1 1 -1 -1 1 -1 -1 1 -1 -1 1 - -Participant's output -9 - -Jury's answer -9 -``` - ### Documentation Documentation can be found at https://python-codeforces.readthedocs.io/en/latest/ From ddf2d3902dbe288f99ef62f9c2c07ba2a1a5478f Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 17:25:59 +0530 Subject: [PATCH 35/44] feat: Add function to get submission by submission id --- codeforces/__init__.py | 5 +++-- codeforces/submission.py | 34 ++++++++++++++++++++++++++++++++++ requirements.txt | 1 + setup.py | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 codeforces/submission.py diff --git a/codeforces/__init__.py b/codeforces/__init__.py index fb83ce6..b7f93e2 100644 --- a/codeforces/__init__.py +++ b/codeforces/__init__.py @@ -2,8 +2,9 @@ Codeforces API wrapper for python """ from . import api +from . import cf_run from . import error from . import problem -from . import cf_run +from . import submission -__all__ = ['error', 'api', 'problem', 'cf_run'] +__all__ = ['api', 'cf_run', 'error', 'problem', 'submission'] diff --git a/codeforces/submission.py b/codeforces/submission.py new file mode 100644 index 0000000..5edb5bf --- /dev/null +++ b/codeforces/submission.py @@ -0,0 +1,34 @@ +import json + +import requests +from bs4 import BeautifulSoup + + +def get_submission(submission_id): + """Get source code associated with a submission_id + + Parameters + ---------- + submission_id: int + Id of the submission. + For example: /contest/1113/submission/**50046519** + + Returns + ------- + source code of the submission + """ + with requests.Session() as sess: + sess.headers.update({'User-Agent': 'python-codeforces/0.1.0'}) + + res = sess.get('https://codeforces.com') + soup = BeautifulSoup(res.text, 'html.parser') + + csrf_token = soup.find('meta', attrs={'name': 'X-Csrf-Token'})['content'] + + res = sess.post( + 'https://codeforces.com/data/submitSource', + {'submissionId': submission_id, 'csrf_token': csrf_token}, + headers={'X-Csrf-Token': csrf_token} + ) + + return json.loads(res.text)['source'] diff --git a/requirements.txt b/requirements.txt index 239ff70..da3c8bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ beautifulsoup4 colorama +requests diff --git a/setup.py b/setup.py index eafbe67..559a1aa 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ author_email='mukundan314@gmail.com', license='MIT', packages=find_packages(exclude=['docs']), - install_requires=['beautifulsoup4', 'colorama'], + install_requires=['beautifulsoup4', 'colorama', 'requests'], extras_requires={'docs': ['sphinx', 'sphinx_rtd_theme']}, entry_points={ 'console_scripts': ['cf-run = codeforces.cf_run:main'] From c78991c2864e1f02a4e7d8ae28ef38fc14e2e551 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 18:05:15 +0530 Subject: [PATCH 36/44] feat: Use requests instead of urllib in api.py --- codeforces/api.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/codeforces/api.py b/codeforces/api.py index 38b3530..abba4af 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -4,9 +4,9 @@ import os import secrets import time -import urllib.error import urllib.parse -import urllib.request + +import requests from . import error @@ -46,32 +46,26 @@ def call(method, key=None, secret=None, **kwargs): A python object containing the results of the api call. """ - args = kwargs.copy() + params = kwargs.copy() if (key is not None) and (secret is not None): - args['time'] = int(time.time()) - args['apiKey'] = key - args['apiSig'] = _generate_api_sig(method, args, secret) - - url_args = urllib.parse.urlencode(args) - url = os.path.join(CODEFORCES_API_URL, "%s?%s" % (method, url_args)) - - try: - with urllib.request.urlopen(url) as res: - data = json.loads(res.read().decode('utf-8')) - except urllib.error.HTTPError as err: - if err.code == 400: - data = json.loads(err.read()) - elif err.code == 404: + params['time'] = int(time.time()) + params['apiKey'] = key + params['apiSig'] = _generate_api_sig(method, params, secret) + + url = os.path.join(CODEFORCES_API_URL, "%s" % method) + + with requests.get(url, params=params) as res: + if res.status_code == 404: data = { 'status': 'FAILED', 'comment': "%s: No such method" % method } - elif err.code in (429, 503): + elif res.status_code in (429, 503): time.sleep(1) return call(method, key, secret, **kwargs) else: - raise + data = json.loads(res.text) if data['status'] == 'FAILED': raise error.CodeforcesAPIError(data['comment'], method, kwargs) From 55964eccf9ed1ec93531e24b4964f1bea73fcd1f Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 18:30:49 +0530 Subject: [PATCH 37/44] feat: Use requests instead of urllib in problem.py --- codeforces/problem.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/codeforces/problem.py b/codeforces/problem.py index ba275b6..a2fdedc 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -1,8 +1,7 @@ """Functions to info about a problem.""" import os -import urllib.error -import urllib.request +import requests from bs4 import BeautifulSoup __all__ = ['get_info'] @@ -42,16 +41,16 @@ def get_info(contest_id, index, gym=False, lang='en'): if gym: problem_url = os.path.join( CODEFORCES_URL, - "gym/%d/problem/%s?lang=%s" % (contest_id, index, lang) + "gym/%d/problem/%s" % (contest_id, index) ) else: problem_url = os.path.join( CODEFORCES_URL, - "contest/%d/problem/%s?lang=%s" % (contest_id, index, lang) + "contest/%d/problem/%s" % (contest_id, index) ) - with urllib.request.urlopen(problem_url) as res: - soup = BeautifulSoup(res.read(), 'html.parser') + with requests.get(problem_url, params={'lang': lang}) as res: + soup = BeautifulSoup(res.text, 'html.parser') title = soup.find_all("div", class_="title")[0].text From 8405248becf1d8ae43de912055bdce898c4aeedc Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 19:08:21 +0530 Subject: [PATCH 38/44] fix(windows): Use urllib.parse.urljoin instead of os.path.join --- codeforces/api.py | 3 +-- codeforces/problem.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/codeforces/api.py b/codeforces/api.py index abba4af..7fb30c8 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -1,7 +1,6 @@ """Functions to call the codeforces api.""" import hashlib import json -import os import secrets import time import urllib.parse @@ -53,7 +52,7 @@ def call(method, key=None, secret=None, **kwargs): params['apiKey'] = key params['apiSig'] = _generate_api_sig(method, params, secret) - url = os.path.join(CODEFORCES_API_URL, "%s" % method) + url = urllib.parse.urljoin(CODEFORCES_API_URL, "%s" % method) with requests.get(url, params=params) as res: if res.status_code == 404: diff --git a/codeforces/problem.py b/codeforces/problem.py index a2fdedc..c085ec8 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -1,5 +1,5 @@ """Functions to info about a problem.""" -import os +import urllib.parse import requests from bs4 import BeautifulSoup @@ -39,12 +39,12 @@ def get_info(contest_id, index, gym=False, lang='en'): """ if gym: - problem_url = os.path.join( + problem_url = urllib.parse.urljoin( CODEFORCES_URL, "gym/%d/problem/%s" % (contest_id, index) ) else: - problem_url = os.path.join( + problem_url = urllib.parse.urljoin( CODEFORCES_URL, "contest/%d/problem/%s" % (contest_id, index) ) From eafb4bd90a8004447a25fccedb0f8747fc791256 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 19:10:08 +0530 Subject: [PATCH 39/44] chore(version): Bump Version (patch) --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0a1e4e5..57b3955 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '0.2' # The full version, including alpha/beta/rc tags -release = '0.2.5' +release = '0.2.6' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index 559a1aa..69ec917 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='python-codeforces', - version='0.2.5', + version='0.2.6', description='Codeforces API wrapper for python', long_description=long_description, long_description_content_type='text/markdown', From 61b96bc38b60e025bce43d1be45c7f09d2805e5c Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Fri, 22 Feb 2019 19:56:48 +0530 Subject: [PATCH 40/44] feat: set shell=True for cf-run --- codeforces/cf_run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/codeforces/cf_run.py b/codeforces/cf_run.py index c92b15d..b2ff772 100644 --- a/codeforces/cf_run.py +++ b/codeforces/cf_run.py @@ -54,6 +54,7 @@ def main(argv=None): out = subprocess.run( args.program, + shell=True, input=inp.encode('utf-8'), capture_output=True, timeout=args.timeout From af07005cd23ae52b274a1a8b053ace8f4dfc50fe Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Sat, 23 Feb 2019 00:18:40 +0530 Subject: [PATCH 41/44] feat: Remove newlines at start when fetching input --- codeforces/problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codeforces/problem.py b/codeforces/problem.py index c085ec8..f558c65 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -58,7 +58,7 @@ def get_info(contest_id, index, gym=False, lang='en'): memory_limit = soup.find_all("div", class_="memory-limit")[0].text[21:] inputs = [ - i.pre.get_text('\n') for i in soup.find_all("div", class_="input") + i.pre.get_text('\n').lstrip('\n') for i in soup.find_all("div", class_="input") ] outputs = [ From dce7363302919150a0cb174443f84706c4498057 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Sat, 23 Feb 2019 00:21:17 +0530 Subject: [PATCH 42/44] fix: fix bugs and refactor code --- codeforces/cf_run.py | 79 +++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/codeforces/cf_run.py b/codeforces/cf_run.py index b2ff772..465bd38 100644 --- a/codeforces/cf_run.py +++ b/codeforces/cf_run.py @@ -1,7 +1,9 @@ #!/usr/bin/env python import argparse +import shlex import subprocess +import tempfile import time import colorama @@ -10,27 +12,42 @@ colorama.init(autoreset=True) + def main(argv=None): parser = argparse.ArgumentParser() - parser.add_argument('contestId', type=int, - help=("Id of the contest. It is not the round number. " - "It can be seen in contest URL.")) - - parser.add_argument('index', type=str, - help=("A letter or a letter followed by a digit, that " - "represent a problem index in a contest.")) - - parser.add_argument('program', type=str, - help="Path to executable that needs to be tested") + parser.add_argument( + 'contestId', + type=int, + help= + "Id of the contest. It is not the round number. It can be seen in contest URL." + ) - parser.add_argument('-t', '--timeout', type=int, default=10, - help=("Timeout for program in seconds, -1 for no time " - "limit (default: 10)")) + parser.add_argument( + 'index', + type=str, + help= + "A letter or a letter followed by a digit, that represent a problem index in a contest." + ) - parser.add_argument('-g', '--gym', action='store_true', - help=("If true open gym contest instead of regular " - "contest. (default: false)")) + parser.add_argument( + 'program', type=str, help="Path to executable that needs to be tested") + + parser.add_argument( + '-t', + '--timeout', + type=int, + default=10, + help= + "Timeout for program in seconds, -1 for no time limit (default: 10)") + + parser.add_argument( + '-g', + '--gym', + action='store_true', + help= + "If true open gym contest instead of regular contest. (default: false)" + ) if argv: args = parser.parse_args(argv) @@ -40,8 +57,7 @@ def main(argv=None): args.timeout = None if args.timeout == -1 else args.timeout title, time_limit, memory_limit, sample_tests = problem.get_info( - args.contestId, args.index, gym=args.gym - ) + args.contestId, args.index, gym=args.gym) print(title) print("time limit per test:", time_limit) @@ -50,15 +66,22 @@ def main(argv=None): print() for inp, ans in sample_tests: + tmp = tempfile.TemporaryFile('w') + tmp.write(inp) + tmp.seek(0) + start = time.time() - out = subprocess.run( - args.program, - shell=True, - input=inp.encode('utf-8'), - capture_output=True, - timeout=args.timeout - ).stdout.decode('utf-8') + proc = subprocess.run( + shlex.split(args.program), + stdin=tmp, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=args.timeout, + universal_newlines=True) + + stdout = proc.stdout + stderr = proc.stderr time_used = time.time() - start @@ -70,7 +93,11 @@ def main(argv=None): print(inp) print(colorama.Style.BRIGHT + "Participant's output") - print(out) + print(stdout) + + if stderr: + print(colorama.Style.BRIGHT + "stderr") + print(stderr) print(colorama.Style.BRIGHT + "Jury's answer") print(ans) From 80bfa097d330c2e4d5fcd81df24e02fbaeea1d73 Mon Sep 17 00:00:00 2001 From: cheran-senthil Date: Sat, 23 Feb 2019 00:45:25 +0530 Subject: [PATCH 43/44] Format Files --- codeforces/api.py | 10 ++-------- codeforces/cf_run.py | 23 ++++++----------------- codeforces/problem.py | 18 ++++-------------- codeforces/submission.py | 9 +++++---- setup.py | 16 +++++----------- test/test_problem.py | 8 ++++---- 6 files changed, 26 insertions(+), 58 deletions(-) diff --git a/codeforces/api.py b/codeforces/api.py index 7fb30c8..ee141c4 100644 --- a/codeforces/api.py +++ b/codeforces/api.py @@ -11,16 +11,13 @@ __all__ = ['call'] - CODEFORCES_API_URL = "https://codeforces.com/api/" def _generate_api_sig(method, args, secret): rand = "%06d" % secrets.randbelow(999999) url_args = urllib.parse.urlencode(sorted(args.items())) - return rand + hashlib.sha512( - ("%s/%s?%s#%s" % (rand, method, url_args, secret)).encode('utf-8') - ).hexdigest() + return rand + hashlib.sha512(("%s/%s?%s#%s" % (rand, method, url_args, secret)).encode('utf-8')).hexdigest() def call(method, key=None, secret=None, **kwargs): @@ -56,10 +53,7 @@ def call(method, key=None, secret=None, **kwargs): with requests.get(url, params=params) as res: if res.status_code == 404: - data = { - 'status': 'FAILED', - 'comment': "%s: No such method" % method - } + data = {'status': 'FAILED', 'comment': "%s: No such method" % method} elif res.status_code in (429, 503): time.sleep(1) return call(method, key, secret, **kwargs) diff --git a/codeforces/cf_run.py b/codeforces/cf_run.py index 465bd38..19d329b 100644 --- a/codeforces/cf_run.py +++ b/codeforces/cf_run.py @@ -17,37 +17,27 @@ def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument( - 'contestId', - type=int, - help= - "Id of the contest. It is not the round number. It can be seen in contest URL." - ) + 'contestId', type=int, help="Id of the contest. It is not the round number. It can be seen in contest URL.") parser.add_argument( 'index', type=str, - help= - "A letter or a letter followed by a digit, that represent a problem index in a contest." - ) + help="A letter or a letter followed by a digit, that represent a problem index in a contest.") - parser.add_argument( - 'program', type=str, help="Path to executable that needs to be tested") + parser.add_argument('program', type=str, help="Path to executable that needs to be tested") parser.add_argument( '-t', '--timeout', type=int, default=10, - help= - "Timeout for program in seconds, -1 for no time limit (default: 10)") + help="Timeout for program in seconds, -1 for no time limit (default: 10)") parser.add_argument( '-g', '--gym', action='store_true', - help= - "If true open gym contest instead of regular contest. (default: false)" - ) + help="If true open gym contest instead of regular contest. (default: false)") if argv: args = parser.parse_args(argv) @@ -56,8 +46,7 @@ def main(argv=None): args.timeout = None if args.timeout == -1 else args.timeout - title, time_limit, memory_limit, sample_tests = problem.get_info( - args.contestId, args.index, gym=args.gym) + title, time_limit, memory_limit, sample_tests = problem.get_info(args.contestId, args.index, gym=args.gym) print(title) print("time limit per test:", time_limit) diff --git a/codeforces/problem.py b/codeforces/problem.py index f558c65..e9ca2b7 100644 --- a/codeforces/problem.py +++ b/codeforces/problem.py @@ -39,15 +39,9 @@ def get_info(contest_id, index, gym=False, lang='en'): """ if gym: - problem_url = urllib.parse.urljoin( - CODEFORCES_URL, - "gym/%d/problem/%s" % (contest_id, index) - ) + problem_url = urllib.parse.urljoin(CODEFORCES_URL, "gym/%d/problem/%s" % (contest_id, index)) else: - problem_url = urllib.parse.urljoin( - CODEFORCES_URL, - "contest/%d/problem/%s" % (contest_id, index) - ) + problem_url = urllib.parse.urljoin(CODEFORCES_URL, "contest/%d/problem/%s" % (contest_id, index)) with requests.get(problem_url, params={'lang': lang}) as res: soup = BeautifulSoup(res.text, 'html.parser') @@ -57,13 +51,9 @@ def get_info(contest_id, index, gym=False, lang='en'): time_limit = soup.find_all("div", class_="time-limit")[0].text[19:] memory_limit = soup.find_all("div", class_="memory-limit")[0].text[21:] - inputs = [ - i.pre.get_text('\n').lstrip('\n') for i in soup.find_all("div", class_="input") - ] + inputs = [i.pre.get_text('\n').lstrip('\n') for i in soup.find_all("div", class_="input")] - outputs = [ - i.pre.get_text('\n') for i in soup.find_all("div", class_="output") - ] + outputs = [i.pre.get_text('\n').lstrip('\n') for i in soup.find_all("div", class_="output")] sample_tests = zip(inputs, outputs) diff --git a/codeforces/submission.py b/codeforces/submission.py index 5edb5bf..6969234 100644 --- a/codeforces/submission.py +++ b/codeforces/submission.py @@ -26,9 +26,10 @@ def get_submission(submission_id): csrf_token = soup.find('meta', attrs={'name': 'X-Csrf-Token'})['content'] res = sess.post( - 'https://codeforces.com/data/submitSource', - {'submissionId': submission_id, 'csrf_token': csrf_token}, - headers={'X-Csrf-Token': csrf_token} - ) + 'https://codeforces.com/data/submitSource', { + 'submissionId': submission_id, + 'csrf_token': csrf_token + }, + headers={'X-Csrf-Token': csrf_token}) return json.loads(res.text)['source'] diff --git a/setup.py b/setup.py index 69ec917..99b2b7d 100644 --- a/setup.py +++ b/setup.py @@ -10,12 +10,9 @@ long_description=long_description, long_description_content_type='text/markdown', classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7' + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7' ], keywords='codeforces', url='https://github.com/Mukundan314/python-codeforces', @@ -25,13 +22,10 @@ packages=find_packages(exclude=['docs']), install_requires=['beautifulsoup4', 'colorama', 'requests'], extras_requires={'docs': ['sphinx', 'sphinx_rtd_theme']}, - entry_points={ - 'console_scripts': ['cf-run = codeforces.cf_run:main'] - }, + entry_points={'console_scripts': ['cf-run = codeforces.cf_run:main']}, python_requires='>=3.6,<4', project_urls={ "Bug Tracker": "https://github.com/Mukundan314/python-codeforces/issues/", "Documentation": "https://python-codeforces.readthedocs.io/en/stable/", "Source": "https://github.com/Mukundan314/python-codeforces" - } -) + }) diff --git a/test/test_problem.py b/test/test_problem.py index a5b1c44..bcf615f 100644 --- a/test/test_problem.py +++ b/test/test_problem.py @@ -9,7 +9,7 @@ def test_normal_problem(self): title, time_limit, memory_limit, sample_tests = codeforces.problem.get_info(contest_id, index) - assert(title == 'A. Theatre Square') - assert(time_limit == '1 second') - assert(memory_limit == '256 megabytes') - assert(list(sample_tests) == [('6 6 4', '4')]) + assert (title == 'A. Theatre Square') + assert (time_limit == '1 second') + assert (memory_limit == '256 megabytes') + assert (list(sample_tests) == [('6 6 4', '4')]) From be956772a7a0307a3ceed4b2610d4fbb6b3822c3 Mon Sep 17 00:00:00 2001 From: Mukundan Senthil Date: Mon, 4 Mar 2019 13:24:56 +0530 Subject: [PATCH 44/44] chore(version): bump version (patch) --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 57b3955..055ed37 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '0.2' # The full version, including alpha/beta/rc tags -release = '0.2.6' +release = '0.2.7' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index 99b2b7d..9ce4540 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='python-codeforces', - version='0.2.6', + version='0.2.7', description='Codeforces API wrapper for python', long_description=long_description, long_description_content_type='text/markdown',