Skip to content

Commit cb58a01

Browse files
adamchainzpfouque
authored andcommitted
Drop support for old Python versions
1 parent cb0d061 commit cb58a01

19 files changed

+72
-127
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ sudo: false
44
cache: pip
55

66
python:
7-
- 2.7
8-
- 3.6
97
- 3.7
108
- 3.8
119
- 3.9

CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ django-fsm unreleased
66

77
- add support for django 4.2
88
- add support for python 3.11
9+
- Drop support for Python < 3.7.
910

1011
django-fsm 2.8.1 2022-08-15
1112
~~~~~~~~~~~~~~~~~~~~~~~~~~~
1213

1314
- Improve fix for get_available_FIELD_transition
1415

15-
1616
django-fsm 2.8.0 2021-11-05
1717
~~~~~~~~~~~~~~~~~~~~~~~~~~
1818

README.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ Or, for the latest git version
4848
4949
$ pip install -e git://github.com/kmmbvnr/django-fsm.git#egg=django-fsm
5050
51-
The library has full Python 3 support
52-
5351
Usage
5452
-----
5553

django_fsm/__init__.py

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
# -*- coding: utf-8 -*-
21
"""
32
State tracking functionality for django models
43
"""
54
import inspect
6-
import sys
7-
from functools import wraps
5+
from functools import partialmethod, wraps
86

97
import django
108
from django.db import models
@@ -13,12 +11,6 @@
1311
from django.db.models.signals import class_prepared
1412
from django_fsm.signals import pre_transition, post_transition
1513

16-
try:
17-
from functools import partialmethod
18-
except ImportError:
19-
# python 2.7, so we are on django<=1.11
20-
from django.utils.functional import curry as partialmethod
21-
2214
try:
2315
from django.apps import apps as django_apps
2416

@@ -46,25 +38,6 @@ def get_model(app_label, model_name):
4638
"RETURN_VALUE",
4739
]
4840

49-
if sys.version_info[:2] == (2, 6):
50-
# Backport of Python 2.7 inspect.getmembers,
51-
# since Python 2.6 ships buggy implementation
52-
def __getmembers(object, predicate=None):
53-
"""Return all members of an object as (name, value) pairs sorted by name.
54-
Optionally, only return members that satisfy a given predicate."""
55-
results = []
56-
for key in dir(object):
57-
try:
58-
value = getattr(object, key)
59-
except AttributeError:
60-
continue
61-
if not predicate or predicate(value):
62-
results.append((key, value))
63-
results.sort()
64-
return results
65-
66-
inspect.getmembers = __getmembers
67-
6841
# South support; see http://south.aeracode.org/docs/tutorial/part4.html#simple-inheritance
6942
try:
7043
from south.modelsinspector import add_introspection_rules
@@ -82,7 +55,7 @@ class TransitionNotAllowed(Exception):
8255
def __init__(self, *args, **kwargs):
8356
self.object = kwargs.pop("object", None)
8457
self.method = kwargs.pop("method", None)
85-
super(TransitionNotAllowed, self).__init__(*args, **kwargs)
58+
super().__init__(*args, **kwargs)
8659

8760

8861
class InvalidResultState(Exception):
@@ -97,7 +70,7 @@ class ConcurrentTransition(Exception):
9770
"""
9871

9972

100-
class Transition(object):
73+
class Transition:
10174
def __init__(self, method, source, target, on_error, conditions, permission, custom):
10275
self.method = method
10376
self.source = source
@@ -166,7 +139,7 @@ def get_available_user_FIELD_transitions(instance, user, field):
166139
yield transition
167140

168141

169-
class FSMMeta(object):
142+
class FSMMeta:
170143
"""
171144
Models methods transitions meta information
172145
"""
@@ -185,7 +158,7 @@ def get_transition(self, source):
185158

186159
def add_transition(self, method, source, target, on_error=None, conditions=[], permission=None, custom={}):
187160
if source in self.transitions:
188-
raise AssertionError("Duplicate transition for {0} state".format(source))
161+
raise AssertionError(f"Duplicate transition for {source} state")
189162

190163
self.transitions[source] = Transition(
191164
method=method,
@@ -237,20 +210,20 @@ def next_state(self, current_state):
237210
transition = self.get_transition(current_state)
238211

239212
if transition is None:
240-
raise TransitionNotAllowed("No transition from {0}".format(current_state))
213+
raise TransitionNotAllowed(f"No transition from {current_state}")
241214

242215
return transition.target
243216

244217
def exception_state(self, current_state):
245218
transition = self.get_transition(current_state)
246219

247220
if transition is None:
248-
raise TransitionNotAllowed("No transition from {0}".format(current_state))
221+
raise TransitionNotAllowed(f"No transition from {current_state}")
249222

250223
return transition.on_error
251224

252225

253-
class FSMFieldDescriptor(object):
226+
class FSMFieldDescriptor:
254227
def __init__(self, field):
255228
self.field = field
256229

@@ -261,14 +234,14 @@ def __get__(self, instance, type=None):
261234

262235
def __set__(self, instance, value):
263236
if self.field.protected and self.field.name in instance.__dict__:
264-
raise AttributeError("Direct {0} modification is not allowed".format(self.field.name))
237+
raise AttributeError(f"Direct {self.field.name} modification is not allowed")
265238

266239
# Update state
267240
self.field.set_proxy(instance, value)
268241
self.field.set_state(instance, value)
269242

270243

271-
class FSMFieldMixin(object):
244+
class FSMFieldMixin:
272245
descriptor_class = FSMFieldDescriptor
273246

274247
def __init__(self, *args, **kwargs):
@@ -288,10 +261,10 @@ def __init__(self, *args, **kwargs):
288261
self.state_proxy[state] = proxy_cls_ref
289262
kwargs["choices"] = choices
290263

291-
super(FSMFieldMixin, self).__init__(*args, **kwargs)
264+
super().__init__(*args, **kwargs)
292265

293266
def deconstruct(self):
294-
name, path, args, kwargs = super(FSMFieldMixin, self).deconstruct()
267+
name, path, args, kwargs = super().deconstruct()
295268
if self.protected:
296269
kwargs["protected"] = self.protected
297270
return name, path, args, kwargs
@@ -337,7 +310,7 @@ def set_proxy(self, instance, state):
337310

338311
model = get_model(app_label, model_name)
339312
if model is None:
340-
raise ValueError("No model found {0}".format(state_proxy))
313+
raise ValueError(f"No model found {state_proxy}")
341314

342315
instance.__class__ = model
343316

@@ -348,13 +321,13 @@ def change_state(self, instance, method, *args, **kwargs):
348321

349322
if not meta.has_transition(current_state):
350323
raise TransitionNotAllowed(
351-
"Can't switch from state '{0}' using method '{1}'".format(current_state, method_name),
324+
f"Can't switch from state '{current_state}' using method '{method_name}'",
352325
object=instance,
353326
method=method,
354327
)
355328
if not meta.conditions_met(instance, current_state):
356329
raise TransitionNotAllowed(
357-
"Transition conditions have not been met for method '{0}'".format(method_name), object=instance, method=method
330+
f"Transition conditions have not been met for method '{method_name}'", object=instance, method=method
358331
)
359332

360333
next_state = meta.next_state(current_state)
@@ -409,15 +382,15 @@ def get_all_transitions(self, instance_cls):
409382
def contribute_to_class(self, cls, name, **kwargs):
410383
self.base_cls = cls
411384

412-
super(FSMFieldMixin, self).contribute_to_class(cls, name, **kwargs)
385+
super().contribute_to_class(cls, name, **kwargs)
413386
setattr(cls, self.name, self.descriptor_class(self))
414-
setattr(cls, "get_all_{0}_transitions".format(self.name), partialmethod(get_all_FIELD_transitions, field=self))
387+
setattr(cls, f"get_all_{self.name}_transitions", partialmethod(get_all_FIELD_transitions, field=self))
415388
setattr(
416-
cls, "get_available_{0}_transitions".format(self.name), partialmethod(get_available_FIELD_transitions, field=self)
389+
cls, f"get_available_{self.name}_transitions", partialmethod(get_available_FIELD_transitions, field=self)
417390
)
418391
setattr(
419392
cls,
420-
"get_available_user_{0}_transitions".format(self.name),
393+
f"get_available_user_{self.name}_transitions",
421394
partialmethod(get_available_user_FIELD_transitions, field=self),
422395
)
423396

@@ -459,7 +432,7 @@ class FSMField(FSMFieldMixin, models.CharField):
459432

460433
def __init__(self, *args, **kwargs):
461434
kwargs.setdefault("max_length", 50)
462-
super(FSMField, self).__init__(*args, **kwargs)
435+
super().__init__(*args, **kwargs)
463436

464437

465438
class FSMIntegerField(FSMFieldMixin, models.IntegerField):
@@ -535,7 +508,7 @@ class ConcurrentTransitionMixin(object):
535508
"""
536509

537510
def __init__(self, *args, **kwargs):
538-
super(ConcurrentTransitionMixin, self).__init__(*args, **kwargs)
511+
super().__init__(*args, **kwargs)
539512
self._update_initial_state()
540513

541514
@property
@@ -550,9 +523,9 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
550523
filter_on = filter(lambda field: field.model == base_qs.model, self.state_fields)
551524

552525
# state filter will be used to narrow down the standard filter checking only PK
553-
state_filter = dict((field.attname, self.__initial_states[field.attname]) for field in filter_on)
526+
state_filter = {field.attname: self.__initial_states[field.attname] for field in filter_on}
554527

555-
updated = super(ConcurrentTransitionMixin, self)._do_update(
528+
updated = super()._do_update(
556529
base_qs=base_qs.filter(**state_filter),
557530
using=using,
558531
pk_val=pk_val,
@@ -573,14 +546,14 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
573546
return updated
574547

575548
def _update_initial_state(self):
576-
self.__initial_states = dict((field.attname, field.value_from_object(self)) for field in self.state_fields)
549+
self.__initial_states = {field.attname: field.value_from_object(self) for field in self.state_fields}
577550

578551
def refresh_from_db(self, *args, **kwargs):
579-
super(ConcurrentTransitionMixin, self).refresh_from_db(*args, **kwargs)
552+
super().refresh_from_db(*args, **kwargs)
580553
self._update_initial_state()
581554

582555
def save(self, *args, **kwargs):
583-
super(ConcurrentTransitionMixin, self).save(*args, **kwargs)
556+
super().save(*args, **kwargs)
584557
self._update_initial_state()
585558

586559

@@ -625,36 +598,34 @@ def can_proceed(bound_method, check_conditions=True):
625598
conditions.
626599
"""
627600
if not hasattr(bound_method, "_django_fsm"):
628-
im_func = getattr(bound_method, "im_func", getattr(bound_method, "__func__"))
629-
raise TypeError("%s method is not transition" % im_func.__name__)
601+
raise TypeError("%s method is not transition" % bound_method.__func__.__name__)
630602

631603
meta = bound_method._django_fsm
632-
im_self = getattr(bound_method, "im_self", getattr(bound_method, "__self__"))
633-
current_state = meta.field.get_state(im_self)
604+
self = bound_method.__self__
605+
current_state = meta.field.get_state(self)
634606

635-
return meta.has_transition(current_state) and (not check_conditions or meta.conditions_met(im_self, current_state))
607+
return meta.has_transition(current_state) and (not check_conditions or meta.conditions_met(self, current_state))
636608

637609

638610
def has_transition_perm(bound_method, user):
639611
"""
640612
Returns True if model in state allows to call bound_method and user have rights on it
641613
"""
642614
if not hasattr(bound_method, "_django_fsm"):
643-
im_func = getattr(bound_method, "im_func", getattr(bound_method, "__func__"))
644-
raise TypeError("%s method is not transition" % im_func.__name__)
615+
raise TypeError("%s method is not transition" % bound_method.__func__.__name__)
645616

646617
meta = bound_method._django_fsm
647-
im_self = getattr(bound_method, "im_self", getattr(bound_method, "__self__"))
648-
current_state = meta.field.get_state(im_self)
618+
self = bound_method.__self__
619+
current_state = meta.field.get_state(self)
649620

650621
return (
651622
meta.has_transition(current_state)
652-
and meta.conditions_met(im_self, current_state)
653-
and meta.has_transition_perm(im_self, current_state, user)
623+
and meta.conditions_met(self, current_state)
624+
and meta.has_transition_perm(self, current_state, user)
654625
)
655626

656627

657-
class State(object):
628+
class State:
658629
def get_state(self, model, transition, result, args=[], kwargs={}):
659630
raise NotImplementedError
660631

@@ -666,7 +637,7 @@ def __init__(self, *allowed_states):
666637
def get_state(self, model, transition, result, args=[], kwargs={}):
667638
if self.allowed_states is not None:
668639
if result not in self.allowed_states:
669-
raise InvalidResultState("{} is not in list of allowed states\n{}".format(result, self.allowed_states))
640+
raise InvalidResultState(f"{result} is not in list of allowed states\n{self.allowed_states}")
670641
return result
671642

672643

@@ -679,5 +650,5 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
679650
result_state = self.func(model, *args, **kwargs)
680651
if self.allowed_states is not None:
681652
if result_state not in self.allowed_states:
682-
raise InvalidResultState("{} is not in list of allowed states\n{}".format(result_state, self.allowed_states))
653+
raise InvalidResultState(f"{result_state} is not in list of allowed states\n{self.allowed_states}")
683654
return result_state

django_fsm/management/commands/graph_transitions.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8; mode: django -*-
21
import graphviz
32
from optparse import make_option
43
from itertools import chain
@@ -37,7 +36,7 @@ def all_fsm_fields_data(model):
3736

3837
def node_name(field, state):
3938
opts = field.model._meta
40-
return "%s.%s.%s.%s" % (opts.app_label, opts.verbose_name.replace(" ", "_"), field.name, state)
39+
return "{}.{}.{}.{}".format(opts.app_label, opts.verbose_name.replace(" ", "_"), field.name, state)
4140

4241

4342
def node_label(field, state):
@@ -79,7 +78,7 @@ def generate_dot(fields_data):
7978
add_transition(source, target, transition.name, source_name, field, sources, targets, edges)
8079

8180
targets.update(
82-
set((node_name(field, target), node_label(field, target)) for target, _ in chain(any_targets, any_except_targets))
81+
{(node_name(field, target), node_label(field, target)) for target, _ in chain(any_targets, any_except_targets)}
8382
)
8483
for target, name in any_targets:
8584
target_name = node_name(field, target)
@@ -91,16 +90,16 @@ def generate_dot(fields_data):
9190
for target, name in any_except_targets:
9291
target_name = node_name(field, target)
9392
all_nodes = sources | targets
94-
all_nodes.remove(((target_name, node_label(field, target))))
93+
all_nodes.remove((target_name, node_label(field, target)))
9594
for source_name, label in all_nodes:
9695
sources.add((source_name, label))
9796
edges.add((source_name, target_name, (("label", name),)))
9897

9998
# construct subgraph
10099
opts = field.model._meta
101100
subgraph = graphviz.Digraph(
102-
name="cluster_%s_%s_%s" % (opts.app_label, opts.object_name, field.name),
103-
graph_attr={"label": "%s.%s.%s" % (opts.app_label, opts.object_name, field.name)},
101+
name=f"cluster_{opts.app_label}_{opts.object_name}_{field.name}",
102+
graph_attr={"label": f"{opts.app_label}.{opts.object_name}.{field.name}"},
104103
)
105104

106105
final_states = targets - sources

django_fsm/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
"""
32
Empty file to mark package as valid django application.
43
"""

django_fsm/signals.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
from django.dispatch import Signal
32

43
pre_transition = Signal()

django_fsm/tests/test_abstract_inheritance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ def test_field_available_transitions_works(self):
5454
def test_field_all_transitions_works(self):
5555
transitions = self.model.get_all_state_transitions()
5656
self.assertEqual(
57-
set([("new", "published"), ("published", "sticked")]), set((data.source, data.target) for data in transitions)
57+
{("new", "published"), ("published", "sticked")}, {(data.source, data.target) for data in transitions}
5858
)

0 commit comments

Comments
 (0)