diff --git a/pyproject.toml b/pyproject.toml index f3ca922..e5a713f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,53 @@ -[tool.black] -skip-string-normalization = true +[tool.ruff] +target-version = "py38" line-length = 79 -exclude = ''' -/( - \.git - | \.venv -)/ -''' + +[tool.ruff.lint] +ignore = [ + "ISC001", + "PLR2004", + "S101", + "S201", +] +select = [ + "A001", + "B", + "C", + "E", + "EXE", + "F", + "G", + "I", + "INP", + "ISC", + "N", + "PGH", + "PIE", + "PL", + "PT", + "RET", + "RUF", + "S", + "SIM", + "T", + "TCH", + "TID25", + "TRY", + "UP", + "W", + # Consider enabling later. + # "ANN", + # "PTH", +] + +[tool.ruff.lint.isort] +combine-as-imports = true +forced-separate = ["tests"] + +[tool.ruff.lint.mccabe] +# Reduce this further. +max-complexity = 24 + +[tool.ruff.lint.pylint] +# Reduce this further. +max-branches = 27 diff --git a/requirements.txt b/requirements.txt index 7f73ac9..166134d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,7 @@ requests-toolbelt==0.8.0 shellingham==1.3.1 six==1.12.0 structlog==20.2.0 -tasktiger==0.18 +tasktiger==0.19.4 tomlkit==0.5.5 urllib3==1.25.6 virtualenv==16.7.5 diff --git a/setup.py b/setup.py index 19c37e9..797ffa7 100644 --- a/setup.py +++ b/setup.py @@ -1,42 +1,40 @@ -# workaround for open() with encoding='' python2/3 compatibility -from io import open from setuptools import setup -with open('README.rst', encoding='utf-8') as file: +with open("README.rst", encoding="utf-8") as file: long_description = file.read() setup( - name='tasktiger-admin', - version='0.3.1', - url='http://github.com/closeio/tasktiger-admin', - license='MIT', - description='Admin for tasktiger, a Python task queue', + name="tasktiger-admin", + version="0.4", + url="http://github.com/closeio/tasktiger-admin", + license="MIT", + description="Admin for tasktiger, a Python task queue", long_description=long_description, - platforms='any', + platforms="any", classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Framework :: Flask', + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Framework :: Flask", ], install_requires=[ - 'click', - 'flask-admin', - 'redis>=2,<5', - 'structlog', - 'tasktiger>=0.11', + "click", + "flask-admin", + "redis>=2,<5", + "structlog", + "tasktiger>=0.19", ], - packages=['tasktiger_admin'], + packages=["tasktiger_admin"], entry_points={ - 'console_scripts': [ - 'tasktiger-admin = tasktiger_admin.utils:run_admin' + "console_scripts": [ + "tasktiger-admin = tasktiger_admin.utils:run_admin" ] }, include_package_data=True, diff --git a/tasktiger_admin/__init__.py b/tasktiger_admin/__init__.py index 309e90f..71c27cd 100644 --- a/tasktiger_admin/__init__.py +++ b/tasktiger_admin/__init__.py @@ -2,8 +2,8 @@ from .views import TaskTigerView -__all__ = ['TaskTigerView', 'tasktiger_admin'] +__all__ = ["TaskTigerView", "tasktiger_admin"] tasktiger_admin = Blueprint( - 'tasktiger_admin', __name__, template_folder='templates' + "tasktiger_admin", __name__, template_folder="templates" ) diff --git a/tasktiger_admin/integrations.py b/tasktiger_admin/integrations.py index b4ce45c..c42e8cc 100644 --- a/tasktiger_admin/integrations.py +++ b/tasktiger_admin/integrations.py @@ -8,15 +8,15 @@ def _get_template_vars(task, execution): info = {} if task: - info['task_id'] = task.id - info['queue'] = task.queue + info["task_id"] = task.id + info["queue"] = task.queue if execution: - info['execution_start'] = _get_time( - execution['time_started'], -TIME_BUFFER + info["execution_start"] = _get_time( + execution["time_started"], -TIME_BUFFER ) - info['execution_failed'] = _get_time( - execution['time_failed'], TIME_BUFFER + info["execution_failed"] = _get_time( + execution["time_failed"], TIME_BUFFER ) return info @@ -43,7 +43,7 @@ def generate_integrations(integration_templates, task, execution): """ integrations = [] for name, url_template in integration_templates: - url_template = jinja2.Template(url_template) - url = url_template.render(**_get_template_vars(task, execution)) + url_jinja_template = jinja2.Template(url_template) + url = url_jinja_template.render(**_get_template_vars(task, execution)) integrations.append((name, url)) return integrations diff --git a/tasktiger_admin/utils.py b/tasktiger_admin/utils.py index 5315be2..1f96e67 100644 --- a/tasktiger_admin/utils.py +++ b/tasktiger_admin/utils.py @@ -3,23 +3,24 @@ from flask import Flask from flask_admin import Admin from tasktiger import TaskTiger + from tasktiger_admin import TaskTigerView @click.command() -@click.option('-h', '--host', help='Redis server hostname') -@click.option('-p', '--port', help='Redis server port') -@click.option('-a', '--password', help='Redis password') -@click.option('-n', '--db', help='Redis database number') -@click.option('-l', '--listen', help='Admin port to listen on') +@click.option("-h", "--host", help="Redis server hostname") +@click.option("-p", "--port", help="Redis server port") +@click.option("-a", "--password", help="Redis password") +@click.option("-n", "--db", help="Redis database number") +@click.option("-l", "--listen", help="Admin port to listen on") def run_admin(host, port, db, password, listen): conn = redis.Redis( host, int(port or 6379), int(db or 0), password, decode_responses=True ) tiger = TaskTiger(setup_structlog=True, connection=conn) app = Flask(__name__) - admin = Admin(app, url='/') + admin = Admin(app, url="/") admin.add_view( - TaskTigerView(tiger, name='TaskTiger', endpoint='tasktiger') + TaskTigerView(tiger, name="TaskTiger", endpoint="tasktiger") ) app.run(debug=True, port=int(listen or 5000)) diff --git a/tasktiger_admin/views.py b/tasktiger_admin/views.py index 9022a04..3d68de4 100644 --- a/tasktiger_admin/views.py +++ b/tasktiger_admin/views.py @@ -19,19 +19,19 @@ def __init__(self, tiger, integration_config=None, *args, **kwargs): integration_config: List of tuples containing integration name and URL """ - super(TaskTigerView, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.tiger = tiger self.integration_config = ( {} if integration_config is None else integration_config ) - @expose('/') + @expose("/") def index(self): queue_stats = self.tiger.get_queue_stats() sorted_stats = sorted(queue_stats.items(), key=lambda k: k[0]) groups = OrderedDict() for queue, stats in sorted_stats: - queue_base = queue.split('.')[0] + queue_base = queue.split(".")[0] if queue_base not in groups: groups[queue_base] = [] groups[queue_base].append((queue, stats)) @@ -39,7 +39,7 @@ def index(self): queue_stats_groups = [] for group_name, queue_stats in groups.items(): group_stats = {} - for queue, stats in queue_stats: + for _, stats in queue_stats: for stat_name, stat_num in stats.items(): if stat_name not in group_stats: group_stats[stat_name] = stat_num @@ -48,38 +48,38 @@ def index(self): queue_stats_groups.append((group_name, group_stats, queue_stats)) return self.render( - 'tasktiger_admin/tasktiger.html', + "tasktiger_admin/tasktiger.html", queue_stats_groups=queue_stats_groups, ) - @expose('///retry/', methods=['POST']) + @expose("///retry/", methods=["POST"]) def task_retry_multiple(self, queue, state): - LIMIT = 50 - n, tasks = Task.tasks_from_queue(self.tiger, queue, state, limit=LIMIT) + limit = 50 + n, tasks = Task.tasks_from_queue(self.tiger, queue, state, limit=limit) for task in tasks: task.retry() - return redirect(url_for('.queue_detail', queue=queue, state=state)) + return redirect(url_for(".queue_detail", queue=queue, state=state)) - @expose('////') + @expose("////") def task_detail(self, queue, state, task_id): - LIMIT = 1000 + limit = 1000 try: task = Task.from_id( - self.tiger, queue, state, task_id, load_executions=LIMIT + self.tiger, queue, state, task_id, load_executions=limit ) except TaskNotFound: abort(404) executions_dumped = [] for execution in task.executions: - traceback = execution.pop('traceback', None) + traceback = execution.pop("traceback", None) execution_integrations = generate_integrations( - self.integration_config.get('EXECUTION_INTEGRATION_LINKS', []), + self.integration_config.get("EXECUTION_INTEGRATION_LINKS", []), task, execution, ) execution_converted = convert_keys_to_datetime( - execution, ['time_failed', 'time_started'] + execution, ["time_failed", "time_started"] ) executions_dumped.append( ( @@ -96,11 +96,11 @@ def task_detail(self, queue, state, task_id): ) integrations = generate_integrations( - self.integration_config.get('INTEGRATION_LINKS', []), task, None + self.integration_config.get("INTEGRATION_LINKS", []), task, None ) return self.render( - 'tasktiger_admin/tasktiger_task_detail.html', + "tasktiger_admin/tasktiger_task_detail.html", queue=queue, state=state, task=task, @@ -110,32 +110,32 @@ def task_detail(self, queue, state, task_id): integrations=integrations, ) - @expose('////retry/', methods=['POST']) + @expose("////retry/", methods=["POST"]) def task_retry(self, queue, state, task_id): try: task = Task.from_id(self.tiger, queue, state, task_id) except TaskNotFound: abort(404) task.retry() - return redirect(url_for('.queue_detail', queue=queue, state=state)) + return redirect(url_for(".queue_detail", queue=queue, state=state)) - @expose('////delete/', methods=['POST']) + @expose("////delete/", methods=["POST"]) def task_delete(self, queue, state, task_id): try: task = Task.from_id(self.tiger, queue, state, task_id) except TaskNotFound: abort(404) task.delete() - return redirect(url_for('.queue_detail', queue=queue, state=state)) + return redirect(url_for(".queue_detail", queue=queue, state=state)) - @expose('///') + @expose("///") def queue_detail(self, queue, state): n, tasks = Task.tasks_from_queue( self.tiger, queue, state, load_executions=1 ) return self.render( - 'tasktiger_admin/tasktiger_queue_detail.html', + "tasktiger_admin/tasktiger_queue_detail.html", queue=queue, state=state, n=n, diff --git a/tests/config.py b/tests/config.py index ccbfa3a..77f4c28 100644 --- a/tests/config.py +++ b/tests/config.py @@ -1,7 +1,7 @@ import os # Redis database number which will be wiped and used for the tests -TEST_DB = int(os.environ.get('REDIS_DB', 1)) +TEST_DB = int(os.environ.get("REDIS_DB", 1)) # Redis hostname -REDIS_HOST = os.environ.get('REDIS_HOST', 'localhost') +REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") diff --git a/tests/test_base.py b/tests/test_base.py index 345537a..8d78096 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -4,10 +4,10 @@ from flask import Flask from flask_admin import Admin from tasktiger import TaskTiger, Worker -from tasktiger_admin import TaskTigerView -from .config import TEST_DB, REDIS_HOST +from tasktiger_admin import TaskTigerView +from .config import REDIS_HOST, TEST_DB conn = redis.Redis(host=REDIS_HOST, db=TEST_DB, decode_responses=True) tiger = TaskTiger(setup_structlog=True, connection=conn) @@ -22,10 +22,10 @@ class BaseTestCase: def setup_method(self, method): conn.flushdb() - self.flask_app = Flask('Test App') - self.flask_app_admin = Admin(self.flask_app, url='/') + self.flask_app = Flask("Test App") + self.flask_app_admin = Admin(self.flask_app, url="/") self.flask_app_admin.add_view( - TaskTigerView(tiger, name='TaskTiger', endpoint='tasktiger') + TaskTigerView(tiger, name="TaskTiger", endpoint="tasktiger") ) self.client = self.flask_app.test_client() @@ -35,11 +35,11 @@ def teardown_method(self, method): class EagerExecution: def __enter__(self): - self.original_value = tiger.config['ALWAYS_EAGER'] - tiger.config['ALWAYS_EAGER'] = True + self.original_value = tiger.config["ALWAYS_EAGER"] + tiger.config["ALWAYS_EAGER"] = True def __exit__(self, type, value, traceback): - tiger.config['ALWAYS_EAGER'] = self.original_value + tiger.config["ALWAYS_EAGER"] = self.original_value class TestCase(BaseTestCase): @@ -61,6 +61,6 @@ def test_basic(self): simple_task.delay() # do a simple get request - response = self.client.get('/') + response = self.client.get("/") assert response.status_code == 200 - assert b'TaskTiger' in response.data + assert b"TaskTiger" in response.data