Skip to content

Commit

Permalink
Merge pull request #188 from ambitioninc/develop
Browse files Browse the repository at this point in the history
6.2.3
  • Loading branch information
jaredlewis authored Aug 16, 2024
2 parents 453e54a + 68be2ad commit 78587f9
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 27 deletions.
67 changes: 42 additions & 25 deletions entity/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,40 +56,56 @@ def wrapper(wrapped, instance, args, kwargs):
return wrapper


@wrapt.decorator
def defer_entity_syncing(wrapped, instance, args, kwargs):
def defer_entity_syncing(*args, handler=None):
"""
A decorator that can be used to defer the syncing of entities until after the method has been run
This is being introduced to help avoid deadlocks in the meantime as we attempt to better understand
why they are happening
A decorator for deferring entity syncing until after the function is complete
An optional handler can be specified to handle the entity syncing.
If no handler is passed the default sync_entities method will be called
"""

# Defer entity syncing while we run our method
sync_entities.defer = True
# Set a default handler
handler = handler or sync_entities

# Run the method
try:
return wrapped(*args, **kwargs)
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
"""
A decorator that can be used to defer the syncing of entities until after the method has been run
This is being introduced to help avoid deadlocks in the meantime as we attempt to better understand
why they are happening
"""

# After we run the method disable the deferred syncing
# and sync all the entities that have been buffered to be synced
finally:
# Enable entity syncing again
sync_entities.defer = False
# Defer entity syncing while we run our method
sync_entities.defer = True

# Get the models that need to be synced
model_objs = list(sync_entities.buffer.values())
# Run the method
try:
return wrapped(*args, **kwargs)

# If none is in the model objects we need to sync all
if None in sync_entities.buffer:
model_objs = list()
# After we run the method disable the deferred syncing
# and sync all the entities that have been buffered to be synced
finally:
# Enable entity syncing again
sync_entities.defer = False

# Sync the entities that were deferred if any
if len(sync_entities.buffer):
sync_entities(*model_objs)
# Get the models that need to be synced
model_objs = list(sync_entities.buffer.values())

# If none is in the model objects we need to sync all
if None in sync_entities.buffer:
model_objs = list()

# Clear the buffer
sync_entities.buffer = {}
# Sync the entities that were deferred if any
if len(sync_entities.buffer):
handler(*model_objs)

# Clear the buffer
sync_entities.buffer = {}

# If the decorator is called without arguments
if len(args) == 1 and callable(args[0]):
return wrapper(args[0])
else:
return wrapper


@wrapt.decorator
Expand Down Expand Up @@ -193,6 +209,7 @@ def sync_entities(*model_objs):
Args:
model_objs (List[Model]): The model objects to sync. If empty, all entities will be synced
"""

if sync_entities.suppress:
# Return false that we did not do anything
return False
Expand Down
22 changes: 21 additions & 1 deletion entity/tests/sync_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
suppress_entity_syncing,
)
from entity.signal_handlers import turn_on_syncing, turn_off_syncing
from unittest.mock import patch, MagicMock, call
from unittest.mock import patch, MagicMock, call, Mock

from entity.tests.models import (
Account, Team, EntityPointer, DummyModel, MultiInheritEntity, AccountConfig, TeamConfig, TeamGroup,
Expand Down Expand Up @@ -1064,6 +1064,26 @@ def test_method(test):
# Ensure that we did not call sync entities
self.assertFalse(mock_sync_entities.called)

def test_defer_custom_handler(self):
# Create a mock handler
mock_handler = Mock()

# Create a test sync method to be decorated
@defer_entity_syncing(handler=mock_handler)
def test_method(count=5):
# Create some entities
for i in range(count):
Account.objects.create()

# Call the test method
test_method(count=5)

# Assert that we called our custom handler
self.assertEqual(Entity.objects.all().count(), 0)
mock_handler.assert_called_once_with(
*Account.objects.all()
)


class SuppressEntitySyncingTests(EntityTestCase):
"""
Expand Down
2 changes: 1 addition & 1 deletion entity/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '6.2.2'
__version__ = '6.2.3'
4 changes: 4 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env python
import sys

# These lines allow nose tests to work in Python 3.10
import collections.abc
collections.Callable = collections.abc.Callable

# Show warnings about django deprecations - uncomment for version upgrade testing
import warnings
from django.utils.deprecation import RemovedInNextVersionWarning
Expand Down
2 changes: 2 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Release Notes

- 6.2.3:
- Update the `defer_entity_syncing` decorator to support an optional handler.
- 6.2.2:
- Fixed entity group logic string for logic sets containing more than 2 items being operated on
- 6.2.1:
Expand Down

0 comments on commit 78587f9

Please sign in to comment.