Skip to content

Commit

Permalink
Enable isort + pytest (#11)
Browse files Browse the repository at this point in the history
Enable pytest
  • Loading branch information
pfouque committed Dec 24, 2023
1 parent d9c9564 commit ca653bf
Show file tree
Hide file tree
Showing 35 changed files with 350 additions and 96 deletions.
12 changes: 8 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ repos:
- repo: https://github.com/python-poetry/poetry
rev: 1.6.1
hooks:
- id: poetry-check
- id: poetry-lock
- id: poetry-export
- id: poetry-check
additional_dependencies:
- poetry-plugin-sort==0.2.0
# FIXME: poetry lock export more platform on the CI
# - id: poetry-lock
# args: ["--no-update"]
- id: poetry-export

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.291
rev: v0.1.9
hooks:
- id: ruff-format
- id: ruff
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ on a model instance with a protected FSMField will cause an exception.

``source`` parameter accepts a list of states, or an individual state or ``django_fsm.State`` implementation.

You can use ``*`` for ``source`` to allow switching to ``target`` from any state.
You can use ``*`` for ``source`` to allow switching to ``target`` from any state.

You can use ``+`` for ``source`` to allow switching to ``target`` from any state excluding ``target`` state.

Expand All @@ -163,7 +163,7 @@ You can use ``+`` for ``source`` to allow switching to ``target`` from any state
``target`` state parameter could point to a specific state or ``django_fsm.State`` implementation

.. code:: python
from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE
@transition(field=state,
source='*',
Expand Down
46 changes: 16 additions & 30 deletions django_fsm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
"""
State tracking functionality for django models
"""
from __future__ import annotations

import inspect
from functools import partialmethod, wraps
from functools import partialmethod
from functools import wraps

import django
from django.apps import apps as django_apps
from django.db import models
from django.db.models import Field
from django.db.models.query_utils import DeferredAttribute
from django.db.models.signals import class_prepared

from django_fsm.signals import pre_transition, post_transition

from django_fsm.signals import post_transition
from django_fsm.signals import pre_transition

__all__ = [
"TransitionNotAllowed",
Expand Down Expand Up @@ -251,26 +253,9 @@ def deconstruct(self):
return name, path, args, kwargs

def get_state(self, instance):
# The state field may be deferred. We delegate the logic of figuring
# this out and loading the deferred field on-demand to Django's
# built-in DeferredAttribute class. DeferredAttribute's instantiation
# signature changed over time, so we need to check Django version
# before proceeding to call DeferredAttribute. An alternative to this
# would be copying the latest implementation of DeferredAttribute to
# django_fsm, but this comes with the added responsibility of keeping
# the copied code up to date.
if django.VERSION[:3] >= (3, 0, 0):
return DeferredAttribute(self).__get__(instance)
elif django.VERSION[:3] >= (2, 1, 0):
return DeferredAttribute(self.name).__get__(instance)
elif django.VERSION[:3] >= (1, 10, 0):
return DeferredAttribute(self.name, model=None).__get__(instance)
else:
# The field was either not deferred (in which case we can return it
# right away) or ir was, but we are running on an unknown version
# of Django and we do not know the appropriate DeferredAttribute
# interface, and accessing the field will raise KeyError.
return instance.__dict__[self.name]
# The state field may be deferred. We delegate the logic of figuring this out
# and loading the deferred field on-demand to Django's built-in DeferredAttribute class.
return DeferredAttribute(self).__get__(instance)

def set_state(self, instance, state):
instance.__dict__[self.name] = state
Expand Down Expand Up @@ -435,14 +420,15 @@ def set_state(self, instance, state):
instance.__dict__[self.attname] = self.to_python(state)


class FSMModelMixin(object):
class FSMModelMixin:
"""
Mixin that allows refresh_from_db for models with fsm protected fields
"""

def _get_protected_fsm_fields(self):
def is_fsm_and_protected(f):
return isinstance(f, FSMFieldMixin) and f.protected

protected_fields = filter(is_fsm_and_protected, self._meta.concrete_fields)
return {f.attname for f in protected_fields}

Expand All @@ -455,13 +441,13 @@ def refresh_from_db(self, *args, **kwargs):
protected_fields = self._get_protected_fsm_fields()
skipped_fields = deferred_fields.union(protected_fields)

fields = [f.attname for f in self._meta.concrete_fields
if f.attname not in skipped_fields]
fields = [f.attname for f in self._meta.concrete_fields if f.attname not in skipped_fields]

kwargs["fields"] = fields
super().refresh_from_db(*args, **kwargs)

kwargs['fields'] = fields
super(FSMModelMixin, self).refresh_from_db(*args, **kwargs)

class ConcurrentTransitionMixin(object):
class ConcurrentTransitionMixin:
"""
Protects a Model from undesirable effects caused by concurrently executed transitions,
e.g. running the same transition multiple times at the same time, or running different
Expand Down
8 changes: 6 additions & 2 deletions django_fsm/management/commands/graph_transitions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import graphviz
from __future__ import annotations

from itertools import chain

import graphviz
from django.apps import apps
from django.core.management.base import BaseCommand
from django.utils.encoding import force_str

from django_fsm import FSMFieldMixin, GET_STATE, RETURN_VALUE
from django_fsm import GET_STATE
from django_fsm import RETURN_VALUE
from django_fsm import FSMFieldMixin


def all_fsm_fields_data(model):
Expand Down
2 changes: 2 additions & 0 deletions django_fsm/signals.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from django.dispatch import Signal

pre_transition = Signal()
Expand Down
6 changes: 5 additions & 1 deletion django_fsm/tests/test_abstract_inheritance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase

from django_fsm import FSMField, transition, can_proceed
from django_fsm import FSMField
from django_fsm import can_proceed
from django_fsm import transition


class BaseAbstractModel(models.Model):
Expand Down
11 changes: 9 additions & 2 deletions django_fsm/tests/test_basic_transitions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase

from django_fsm import FSMField, TransitionNotAllowed, transition, can_proceed, Transition
from django_fsm.signals import pre_transition, post_transition
from django_fsm import FSMField
from django_fsm import Transition
from django_fsm import TransitionNotAllowed
from django_fsm import can_proceed
from django_fsm import transition
from django_fsm.signals import post_transition
from django_fsm.signals import pre_transition


class BlogPost(models.Model):
Expand Down
8 changes: 7 additions & 1 deletion django_fsm/tests/test_conditions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase
from django_fsm import FSMField, TransitionNotAllowed, transition, can_proceed

from django_fsm import FSMField
from django_fsm import TransitionNotAllowed
from django_fsm import can_proceed
from django_fsm import transition


def condition_func(instance):
Expand Down
7 changes: 6 additions & 1 deletion django_fsm/tests/test_integer_field.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase
from django_fsm import FSMIntegerField, TransitionNotAllowed, transition

from django_fsm import FSMIntegerField
from django_fsm import TransitionNotAllowed
from django_fsm import transition


class BlogPostStateEnum:
Expand Down
7 changes: 6 additions & 1 deletion django_fsm/tests/test_key_field.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase
from django_fsm import FSMKeyField, TransitionNotAllowed, transition, can_proceed

from django_fsm import FSMKeyField
from django_fsm import TransitionNotAllowed
from django_fsm import can_proceed
from django_fsm import transition

FK_AVAILABLE_STATES = (
("New", "_NEW_"),
Expand Down
5 changes: 4 additions & 1 deletion django_fsm/tests/test_protected_field.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase

from django_fsm import FSMField, transition
from django_fsm import FSMField
from django_fsm import transition


class ProtectedAccessModel(models.Model):
Expand Down
18 changes: 11 additions & 7 deletions django_fsm/tests/test_protected_fields.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
from __future__ import annotations

import unittest

import django
from django.db import models
from django.test import TestCase

from django_fsm import FSMField, FSMModelMixin, transition
from django_fsm import FSMField
from django_fsm import FSMModelMixin
from django_fsm import transition


class RefreshableProtectedAccessModel(models.Model):
status = FSMField(default='new', protected=True)
status = FSMField(default="new", protected=True)

@transition(field=status, source='new', target='published')
@transition(field=status, source="new", target="published")
def publish(self):
pass

class Meta:
app_label = 'django_fsm'
app_label = "django_fsm"


class RefreshableModel(FSMModelMixin, RefreshableProtectedAccessModel):
Expand All @@ -25,16 +29,16 @@ class RefreshableModel(FSMModelMixin, RefreshableProtectedAccessModel):
class TestDirectAccessModels(TestCase):
def test_no_direct_access(self):
instance = RefreshableProtectedAccessModel()
self.assertEqual(instance.status, 'new')
self.assertEqual(instance.status, "new")

def try_change():
instance.status = 'change'
instance.status = "change"

self.assertRaises(AttributeError, try_change)

instance.publish()
instance.save()
self.assertEqual(instance.status, 'published')
self.assertEqual(instance.status, "published")

@unittest.skipIf(django.VERSION < (1, 8), "Django introduced refresh_from_db in 1.8")
def test_refresh_from_db(self):
Expand Down
6 changes: 5 additions & 1 deletion django_fsm/tests/test_proxy_inheritance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

from django.db import models
from django.test import TestCase

from django_fsm import FSMField, transition, can_proceed
from django_fsm import FSMField
from django_fsm import can_proceed
from django_fsm import transition


class BaseModel(models.Model):
Expand Down
Loading

0 comments on commit ca653bf

Please sign in to comment.