Skip to content

Commit 1385831

Browse files
committed
Resolve CodeQL warnings & drop Python 3.8
1 parent 9a082e2 commit 1385831

17 files changed

+76
-98
lines changed

.github/workflows/ci.yml

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ jobs:
5959
strategy:
6060
matrix:
6161
python-version:
62-
- "3.8"
6362
- "3.9"
6463
- "3.10"
6564
- "3.11"

.readthedocs.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ version: 2
77
build:
88
os: ubuntu-20.04
99
tools:
10-
python: "3.9"
10+
python: "3.11"
1111
apt_packages:
1212
- graphviz
1313

docs/conf.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,10 @@ def linkcode_resolve(domain, info):
5050
try:
5151
lines, first_line = inspect.getsourcelines(item)
5252
lineno = "#L%d-L%s" % (first_line, first_line + len(lines) - 1)
53-
except (TypeError, IOError):
53+
except (TypeError, OSError):
5454
pass
55-
return "https://github.com/%s/%s/blob/%s/%s.py%s" % (
56-
github_user,
57-
project,
58-
head,
59-
filename,
60-
lineno,
55+
return (
56+
f"https://github.com/{github_user}/{project}/blob/{head}/{filename}.py{lineno}"
6157
)
6258

6359

joeflow/__init__.py

-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
"""The lean workflow automation framework for machines with heart."""
2-
import django
3-
42
from . import _version
53

64
__version__ = _version.version
75
VERSION = _version.version_tuple
8-
9-
if django.VERSION < (3, 2):
10-
default_app_config = "joeflow.apps.JoeflowConfig"

joeflow/admin.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
__all__ = ("WorkflowAdmin",)
1111

1212

13+
@admin.action(
14+
description=t("Rerun selected tasks"),
15+
permissions=("rerun",),
16+
)
1317
def rerun(modeladmin, request, queryset):
1418
succeeded = queryset.succeeded().count()
1519
if succeeded:
@@ -24,10 +28,10 @@ def rerun(modeladmin, request, queryset):
2428
messages.success(request, "%s tasks have been successfully queued" % counter)
2529

2630

27-
rerun.short_description = t("Rerun selected tasks")
28-
rerun.allowed_permissions = ("rerun",)
29-
30-
31+
@admin.action(
32+
description=t("Cancel selected tasks"),
33+
permissions=("cancel",),
34+
)
3135
def cancel(modeladmin, request, queryset):
3236
not_scheduled = queryset.not_scheduled().count()
3337
if not_scheduled:
@@ -40,32 +44,26 @@ def cancel(modeladmin, request, queryset):
4044
messages.success(request, "Tasks have been successfully canceled")
4145

4246

43-
cancel.short_description = t("Cancel selected tasks")
44-
cancel.allowed_permissions = ("cancel",)
45-
46-
4747
@admin.register(models.Task)
4848
class TaskAdmin(VersionAdmin):
4949
def has_rerun_permission(self, request):
5050
opts = self.opts
5151
codename = get_permission_codename("rerun", opts)
52-
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
52+
return request.user.has_perm(f"{opts.app_label}.{codename}")
5353

5454
def has_cancel_permission(self, request):
5555
opts = self.opts
5656
codename = get_permission_codename("cancel", opts)
57-
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
57+
return request.user.has_perm(f"{opts.app_label}.{codename}")
5858

59+
@admin.display(description=t("Traceback"))
5960
def pretty_stacktrace(self, obj):
6061
return format_html('<pre class="readonly collapse">{}<pre>', obj.stacktrace)
6162

62-
pretty_stacktrace.short_description = t("Traceback")
63-
63+
@admin.display(description=t("Child tasks"))
6464
def child_tasks(self, obj):
6565
return ", ".join(str(task) for task in obj.child_task_set.all().iterator())
6666

67-
child_tasks.short_description = t("Child tasks")
68-
6967
actions = (rerun, cancel)
7068

7169
list_display = (

joeflow/contrib/reversion.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from contextlib import contextmanager
22

3-
from joeflow import utils
3+
import joeflow.models
44

55
__all__ = (
66
"register_workflows",
@@ -16,7 +16,7 @@ def register_workflows():
1616
except ImportError:
1717
pass
1818
else:
19-
for workflow in utils.get_workflows():
19+
for workflow in joeflow.models.get_workflows():
2020
if not revisions.is_registered(workflow):
2121
revisions.register(workflow)
2222

joeflow/forms.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.utils.translation import gettext_lazy as t
33

44
from . import models
5+
from .typing import HUMAN
56

67

78
class OverrideForm(forms.ModelForm):
@@ -35,7 +36,7 @@ def start_next_tasks(self, user=None):
3536
parent_tasks = []
3637
override_task = self.instance.task_set.create(
3738
name="override",
38-
type=models.Task.HUMAN,
39+
type=HUMAN,
3940
)
4041
override_task.parent_task_set.set(parent_tasks)
4142
override_task.finish(user=user)

joeflow/management/commands/render_workflow_graph.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from django.core.management import BaseCommand
22

3-
from joeflow import utils
4-
from joeflow.models import Workflow
3+
import joeflow.models
54

65

76
class Command(BaseCommand):
@@ -45,10 +44,12 @@ def handle(self, *args, **options):
4544
cleanup = options["cleanup"]
4645
directory = options.get("directory", None)
4746

48-
workflows = [utils.get_workflow(s) for s in workflows] or utils.get_workflows()
47+
workflows = [
48+
joeflow.models.get_workflow(s) for s in workflows
49+
] or joeflow.models.get_workflows()
4950

5051
for workflow in filter(None, workflows):
51-
if workflow != Workflow:
52+
if workflow != joeflow.models.Workflow:
5253
opt = workflow._meta
5354
if verbosity > 0:
5455
self.stdout.write(

joeflow/models.py

+38-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import logging
22
import sys
33
import traceback
4-
from typing import Any, List, Tuple
4+
import types
5+
import typing
56

67
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
78
from django.db import models, transaction
@@ -15,8 +16,8 @@
1516
from django.views import View
1617
from django.views.generic.edit import BaseCreateView
1718

18-
from . import utils
1919
from .conf import settings
20+
from .typing import HUMAN, MACHINE
2021
from .utils import NoDashDiGraph
2122

2223
logger = logging.getLogger(__name__)
@@ -42,10 +43,10 @@ def __new__(mcs, name, bases, attrs):
4243
if func in nodes:
4344
node = getattr(klass, name)
4445
node.name = name
45-
node.type = getattr(node, "type", "machine")
46+
node.type = getattr(node, "type", MACHINE)
4647
node.workflow_cls = klass
4748
except TypeError:
48-
pass
49+
pass # not a function
4950
if "override_view" in attrs and isinstance(klass.override_view, str):
5051
klass.override_view = import_string(klass.override_view)
5152
if "detail_view" in attrs and isinstance(klass.detail_view, str):
@@ -82,7 +83,7 @@ def save(self, **kwargs):
8283
update_fields.append("modified")
8384
super().save(**kwargs)
8485

85-
edges: List[Tuple[Any, Any]] = None
86+
edges: list[tuple[typing.Any, typing.Any]] = None
8687
"""
8788
Edges define the transitions between tasks.
8889
@@ -136,9 +137,9 @@ def urls(cls):
136137
for name, node in cls.get_nodes():
137138
if isinstance(node, View):
138139
if isinstance(node, BaseCreateView):
139-
route = "{name}/".format(name=name)
140+
route = f"{name}/"
140141
else:
141-
route = "{name}/<int:pk>/".format(name=name)
142+
route = f"{name}/<int:pk>/"
142143
urls.append(
143144
path(
144145
route + node.path,
@@ -177,15 +178,11 @@ def get_url_namespace(cls):
177178

178179
def get_absolute_url(self):
179180
"""Return URL to workflow detail view."""
180-
return reverse(
181-
"{}:detail".format(self.get_url_namespace()), kwargs=dict(pk=self.pk)
182-
)
181+
return reverse(f"{self.get_url_namespace()}:detail", kwargs=dict(pk=self.pk))
183182

184183
def get_override_url(self):
185184
"""Return URL to workflow override view."""
186-
return reverse(
187-
"{}:override".format(self.get_url_namespace()), kwargs=dict(pk=self.pk)
188-
)
185+
return reverse(f"{self.get_url_namespace()}:override", kwargs=dict(pk=self.pk))
189186

190187
@classmethod
191188
def get_graph(cls, color="black"):
@@ -206,7 +203,7 @@ def get_graph(cls, color="black"):
206203
)
207204
for name, node in cls.get_nodes():
208205
node_style = "filled"
209-
if node.type == "human":
206+
if node.type == HUMAN:
210207
node_style += ", rounded"
211208
graph.node(name, style=node_style, color=color, fontcolor=color)
212209

@@ -252,7 +249,7 @@ def get_instance_graph(self):
252249
style = "filled"
253250
peripheries = "1"
254251

255-
if task.type == "human":
252+
if task.type == HUMAN:
256253
style += ", rounded"
257254
if not task.completed:
258255
style += ", bold"
@@ -285,7 +282,7 @@ def get_instance_graph(self):
285282
for task in self.task_set.exclude(name__in=names).exclude(name="override"):
286283
style = "filled, dashed"
287284
peripheries = "1"
288-
if task.type == "human":
285+
if task.type == HUMAN:
289286
style += ", rounded"
290287
if not task.completed:
291288
style += ", bold"
@@ -340,7 +337,7 @@ def workflow_state_subclasses():
340337

341338
apps.check_models_ready()
342339
query = models.Q()
343-
for workflow in utils.get_workflows():
340+
for workflow in get_workflows():
344341
opts = workflow._meta
345342
query |= models.Q(app_label=opts.app_label, model=opts.model_name)
346343
return query
@@ -396,8 +393,6 @@ class Task(models.Model):
396393

397394
name = models.CharField(max_length=255, db_index=True, editable=False)
398395

399-
HUMAN = "human"
400-
MACHINE = "machine"
401396
_type_choices = (
402397
(HUMAN, t(HUMAN)),
403398
(MACHINE, t(MACHINE)),
@@ -470,7 +465,7 @@ class Meta:
470465
default_manager_name = "objects"
471466

472467
def __str__(self):
473-
return "%s (%s)" % (self.name, self.pk)
468+
return f"{self.name} ({self.pk})"
474469

475470
def save(self, **kwargs):
476471
if self.pk:
@@ -486,12 +481,12 @@ def save(self, **kwargs):
486481

487482
def get_absolute_url(self):
488483
if self.completed:
489-
return
490-
url_name = "{}:{}".format(self.workflow.get_url_namespace(), self.name)
484+
return # completed tasks have no detail view
485+
url_name = f"{self.workflow.get_url_namespace()}:{self.name}"
491486
try:
492487
return reverse(url_name, kwargs=dict(pk=self.pk))
493488
except NoReverseMatch:
494-
pass
489+
return # no URL was defined for this task
495490

496491
@property
497492
def node(self):
@@ -582,3 +577,23 @@ def start_next_tasks(self, next_nodes: list = None):
582577
transaction.on_commit(task.enqueue)
583578
tasks.append(task)
584579
return tasks
580+
581+
582+
def get_workflows() -> types.GeneratorType:
583+
"""Return all registered workflows."""
584+
from django.apps import apps
585+
586+
apps.check_models_ready()
587+
for model in apps.get_models():
588+
if issubclass(model, Workflow) and model is not Workflow and model.edges:
589+
yield model
590+
return # empty generator
591+
592+
593+
def get_workflow(name) -> typing.Optional[Workflow]:
594+
for workflow_cls in get_workflows():
595+
if (
596+
name.lower()
597+
== f"{workflow_cls._meta.app_label}.{workflow_cls.__name__}".lower()
598+
):
599+
return workflow_cls

joeflow/tasks/__init__.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
A task can be considered as a simple transaction that changes state of a workflow.
55
There are two types of tasks, human and machine tasks.
66
"""
7+
from joeflow.typing import * # NoQA
8+
79
from .human import * # NoQA
810
from .machine import * # NoQA
9-
10-
HUMAN = "human"
11-
MACHINE = "machine"

joeflow/tasks/machine.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Set of reusable machine tasks."""
2-
from typing import Iterable
2+
from collections.abc import Iterable
33

44
from django.utils import timezone
55

joeflow/typing.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
HUMAN = "human"
2+
MACHINE = "machine"

joeflow/utils.py

-22
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,8 @@
1-
import types
21
from collections import defaultdict
32

43
import graphviz as gv
54

65

7-
def get_workflows() -> types.GeneratorType:
8-
"""Return all registered workflows."""
9-
from django.apps import apps
10-
11-
from .models import Workflow
12-
13-
apps.check_models_ready()
14-
for model in apps.get_models():
15-
if issubclass(model, Workflow) and model is not Workflow and model.edges:
16-
yield model
17-
18-
19-
def get_workflow(name):
20-
for workflow_cls in get_workflows():
21-
if (
22-
name.lower()
23-
== f"{workflow_cls._meta.app_label}.{workflow_cls.__name__}".lower()
24-
):
25-
return workflow_cls
26-
27-
286
class NoDashDiGraph(gv.Digraph):
297
"""
308
Like `.graphviz.Digraph` but with unique nodes and edges.

0 commit comments

Comments
 (0)