django-queuebie 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/CHANGES.md +3 -0
  2. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/PKG-INFO +1 -1
  3. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/__init__.py +1 -1
  4. django_queuebie-0.3.0/queuebie/runner.py +63 -0
  5. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/handlers/commands/testapp.py +13 -5
  6. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/messages/commands/my_commands.py +10 -0
  7. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_registry.py +1 -1
  8. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_runner.py +11 -11
  9. django_queuebie-0.2.0/queuebie/runner.py +0 -64
  10. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.ambient-package-update/metadata.py +0 -0
  11. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.ambient-package-update/templates/snippets/content.tpl +0 -0
  12. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.ambient-package-update/templates/snippets/tagline.tpl +0 -0
  13. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.editorconfig +0 -0
  14. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.github/workflows/ci.yml +0 -0
  15. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.gitignore +0 -0
  16. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.pre-commit-config.yaml +0 -0
  17. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/.readthedocs.yaml +0 -0
  18. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/LICENSE +0 -0
  19. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/LICENSE.md +0 -0
  20. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/MANIFEST.in +0 -0
  21. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/README.md +0 -0
  22. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/SECURITY.md +0 -0
  23. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/Makefile +0 -0
  24. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/conf.py +0 -0
  25. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/features/changelog.rst +0 -0
  26. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/features/getting-started.md +0 -0
  27. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/features/introduction.md +0 -0
  28. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/features/motivation.md +0 -0
  29. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/features/settings.md +0 -0
  30. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/features/setup.md +0 -0
  31. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/index.rst +0 -0
  32. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/docs/make.bat +0 -0
  33. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/manage.py +0 -0
  34. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/pyproject.toml +0 -0
  35. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/apps.py +0 -0
  36. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/database_blocker.py +0 -0
  37. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/exceptions.py +0 -0
  38. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/logger.py +0 -0
  39. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/management/__init__.py +0 -0
  40. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/management/commands/__init__.py +0 -0
  41. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/management/commands/clear_queuebie_registry.py +0 -0
  42. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/messages.py +0 -0
  43. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/registry.py +0 -0
  44. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/settings.py +0 -0
  45. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/queuebie/utils.py +0 -0
  46. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/scripts/unix/install_requirements.sh +0 -0
  47. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/scripts/unix/publish_to_pypi.sh +0 -0
  48. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/scripts/windows/install_requirements.ps1 +0 -0
  49. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/scripts/windows/publish_to_pypi.ps1 +0 -0
  50. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/settings.py +0 -0
  51. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/__init__.py +0 -0
  52. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/handlers/__init__.py +0 -0
  53. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/handlers/commands/__init__.py +0 -0
  54. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/handlers/events/__init__.py +0 -0
  55. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/handlers/events/testapp.py +0 -0
  56. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/messages/__init__.py +0 -0
  57. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/messages/commands/__init__.py +0 -0
  58. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/messages/commands/other_commands.py +0 -0
  59. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/messages/events/__init__.py +0 -0
  60. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/messages/events/my_events.py +0 -0
  61. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/testapp/urls.py +0 -0
  62. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/__init__.py +0 -0
  63. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/helpers/__init__.py +0 -0
  64. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/helpers/commands.py +0 -0
  65. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/helpers/events.py +0 -0
  66. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/management_commands/__init__.py +0 -0
  67. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/management_commands/test_clear_queuebie_registry.py +0 -0
  68. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_apps.py +0 -0
  69. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_database_blocker.py +0 -0
  70. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_exceptions.py +0 -0
  71. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_logger.py +0 -0
  72. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_messages.py +0 -0
  73. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_settings.py +0 -0
  74. {django_queuebie-0.2.0 → django_queuebie-0.3.0}/tests/test_utils.py +0 -0
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ * *0.3.0* (2025-03-19)
4
+ * The whole queue iteration now is wrapped in a transaction atomic
5
+
3
6
  * *0.2.0* (2025-03-17)
4
7
  * Extend strict mode to prohibit event (!) handlers to talk to the database
5
8
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-queuebie
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Simple message queue for commands and events (CQRS)
5
5
  Author-email: Ambient Digital <hello@ambient.digital>
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  """Simple message queue for commands and events (CQRS)"""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
4
4
 
5
5
  from queuebie.registry import MessageRegistry
6
6
 
@@ -0,0 +1,63 @@
1
+ import importlib
2
+ from contextlib import nullcontext
3
+
4
+ from django.db import transaction
5
+
6
+ from queuebie import message_registry
7
+ from queuebie.database_blocker import BlockDatabaseAccess
8
+ from queuebie.exceptions import InvalidMessageTypeError
9
+ from queuebie.logger import get_logger
10
+ from queuebie.messages import Command, Event, Message
11
+ from queuebie.settings import get_queuebie_strict_mode
12
+
13
+
14
+ def handle_message(messages: Message | list[Message]) -> None:
15
+ queue: list[Message] = messages if isinstance(messages, list) else [messages]
16
+
17
+ for message in queue:
18
+ if not isinstance(message, (Command, Event)):
19
+ raise InvalidMessageTypeError(class_name=message.__class__.__name__)
20
+
21
+ # Run auto-registry
22
+ message_registry.autodiscover()
23
+
24
+ with transaction.atomic():
25
+ while queue:
26
+ message = queue.pop(0)
27
+ if isinstance(message, Command):
28
+ handler_list = message_registry.command_dict.get(message.module_path(), [])
29
+ block_db_access = False
30
+ else:
31
+ handler_list = message_registry.event_dict.get(message.module_path(), [])
32
+ block_db_access = True if get_queuebie_strict_mode() else False
33
+
34
+ new_messages = _process_message(handler_list=handler_list, message=message, block_db_access=block_db_access)
35
+ queue.extend(new_messages)
36
+
37
+
38
+ def _process_message(*, handler_list: list, message: [Command, Event], block_db_access: bool) -> list[Message]:
39
+ """
40
+ Handler to process messages of type "Command"
41
+ """
42
+ logger = get_logger()
43
+ messages = []
44
+
45
+ for handler in handler_list:
46
+ try:
47
+ logger.debug(
48
+ f"Handling command '{message.module_path()}' ({message.uuid}) with handler '{handler['name']}'."
49
+ )
50
+ module = importlib.import_module(handler["module"])
51
+ handler_function = getattr(module, handler["name"])
52
+ with BlockDatabaseAccess() if block_db_access else nullcontext():
53
+ handler_messages = handler_function(context=message) or []
54
+ handler_messages = handler_messages if isinstance(handler_messages, list) else [handler_messages]
55
+ if len(handler_messages) > 0:
56
+ messages.extend(handler_messages)
57
+ uuid_list = [f"{m!s}" for m in handler_messages]
58
+ logger.debug(f"New messages: {uuid_list!s}")
59
+ except Exception as e:
60
+ logger.debug(f"Exception handling command {message.module_path()}: {e!s}")
61
+ raise e from e
62
+
63
+ return messages
@@ -5,7 +5,13 @@ from django.contrib.auth.models import User
5
5
  from queuebie import message_registry
6
6
  from queuebie.logger import get_logger
7
7
  from queuebie.messages import Event
8
- from testapp.messages.commands.my_commands import CriticalCommand, DoSomething, PersistSomething
8
+ from testapp.messages.commands.my_commands import (
9
+ CreateUser,
10
+ CriticalCommand,
11
+ DoSomething,
12
+ PersistSomething,
13
+ RaiseRuntimeError,
14
+ )
9
15
  from testapp.messages.events.my_events import SomethingHappened, SomethingHappenedThatWantsToBePersisted
10
16
 
11
17
 
@@ -29,9 +35,11 @@ def handle_critical_command(*, context: CriticalCommand) -> None:
29
35
  raise RuntimeError("Handler is broken.") # noqa: TRY003
30
36
 
31
37
 
32
- def create_user(*args, **kwargs):
33
- return User.objects.create_user(username="username")
38
+ @message_registry.register_command(command=CreateUser)
39
+ def create_user(context: CreateUser):
40
+ User.objects.create_user(username=context.username)
34
41
 
35
42
 
36
- def raise_exception(*args, **kwargs):
37
- raise RuntimeError("Something is broken.") # noqa: TRY003
43
+ @message_registry.register_command(command=RaiseRuntimeError)
44
+ def raise_exception(context: RaiseRuntimeError):
45
+ raise RuntimeError(context.error_msg)
@@ -21,3 +21,13 @@ class SameNameCommand(Command):
21
21
  @dataclass(kw_only=True)
22
22
  class PersistSomething(Command):
23
23
  any_var: str
24
+
25
+
26
+ @dataclass(kw_only=True)
27
+ class CreateUser(Command):
28
+ username: str
29
+
30
+
31
+ @dataclass(kw_only=True)
32
+ class RaiseRuntimeError(Command):
33
+ error_msg: str
@@ -124,7 +124,7 @@ def test_message_autodiscover_regular():
124
124
  message_registry.autodiscover()
125
125
 
126
126
  # Assert one command registered
127
- assert len(message_registry.command_dict) == 3 # noqa: PLR2004
127
+ assert len(message_registry.command_dict) == 5 # noqa: PLR2004
128
128
  assert DoSomething.module_path() in message_registry.command_dict.keys()
129
129
  assert CriticalCommand.module_path() in message_registry.command_dict.keys()
130
130
 
@@ -8,8 +8,15 @@ from django.test import override_settings
8
8
  from queuebie import MessageRegistry
9
9
  from queuebie.database_blocker import DatabaseAccessDeniedError
10
10
  from queuebie.exceptions import InvalidMessageTypeError
11
- from queuebie.runner import _process_message, handle_message
12
- from testapp.messages.commands.my_commands import CriticalCommand, DoSomething, PersistSomething, SameNameCommand
11
+ from queuebie.runner import handle_message
12
+ from testapp.messages.commands.my_commands import (
13
+ CreateUser,
14
+ CriticalCommand,
15
+ DoSomething,
16
+ PersistSomething,
17
+ RaiseRuntimeError,
18
+ SameNameCommand,
19
+ )
13
20
  from testapp.messages.events.my_events import (
14
21
  SomethingHappened,
15
22
  SomethingHappenedThatWantsToBePersistedViaEvent,
@@ -125,15 +132,8 @@ def test_handle_message_other_command_with_same_name(mocked_handle_command, *arg
125
132
 
126
133
  @pytest.mark.django_db
127
134
  @mock.patch("queuebie.registry.get_queuebie_strict_mode", return_value=False)
128
- def test_process_message_atomic_works(mocked_handle_command, *args):
129
- handler_list = [
130
- {"module": "testapp.handlers.commands.testapp", "name": "create_user"},
131
- {"module": "testapp.handlers.commands.testapp", "name": "raise_exception"},
132
- ]
133
-
134
- message = DoSomething(my_var=1)
135
-
135
+ def test_handle_message_atomic_works(*args):
136
136
  with pytest.raises(RuntimeError, match="Something is broken."):
137
- _process_message(handler_list=handler_list, message=message, block_db_access=False)
137
+ handle_message([CreateUser(username="username"), RaiseRuntimeError(error_msg="Something is broken.")])
138
138
 
139
139
  assert User.objects.filter(username="username").exists() is False
@@ -1,64 +0,0 @@
1
- import importlib
2
- from contextlib import nullcontext
3
-
4
- from django.db import transaction
5
-
6
- from queuebie import message_registry
7
- from queuebie.database_blocker import BlockDatabaseAccess
8
- from queuebie.exceptions import InvalidMessageTypeError
9
- from queuebie.logger import get_logger
10
- from queuebie.messages import Command, Event, Message
11
- from queuebie.settings import get_queuebie_strict_mode
12
-
13
-
14
- def handle_message(messages: Message | list[Message]) -> None:
15
- queue: list[Message] = messages if isinstance(messages, list) else [messages]
16
-
17
- for message in queue:
18
- if not isinstance(message, (Command, Event)):
19
- raise InvalidMessageTypeError(class_name=message.__class__.__name__)
20
-
21
- # Run auto-registry
22
- message_registry.autodiscover()
23
-
24
- while queue:
25
- message = queue.pop(0)
26
- if isinstance(message, Command):
27
- handler_list = message_registry.command_dict.get(message.module_path(), [])
28
- block_db_access = False
29
- else:
30
- handler_list = message_registry.event_dict.get(message.module_path(), [])
31
- block_db_access = True if get_queuebie_strict_mode() else False
32
-
33
- new_messages = _process_message(handler_list=handler_list, message=message, block_db_access=block_db_access)
34
- queue.extend(new_messages)
35
-
36
-
37
- def _process_message(*, handler_list: list, message: [Command, Event], block_db_access: bool) -> list[Message]:
38
- """
39
- Handler to process messages of type "Command"
40
- """
41
- logger = get_logger()
42
- messages = []
43
-
44
- # TODO: should the whole chain be atomic and not just the handler?
45
- with transaction.atomic():
46
- for handler in handler_list:
47
- try:
48
- logger.debug(
49
- f"Handling command '{message.module_path()}' ({message.uuid}) with handler '{handler['name']}'."
50
- )
51
- module = importlib.import_module(handler["module"])
52
- handler_function = getattr(module, handler["name"])
53
- with BlockDatabaseAccess() if block_db_access else nullcontext():
54
- handler_messages = handler_function(context=message) or []
55
- handler_messages = handler_messages if isinstance(handler_messages, list) else [handler_messages]
56
- if len(handler_messages) > 0:
57
- messages.extend(handler_messages)
58
- uuid_list = [f"{m!s}" for m in handler_messages]
59
- logger.debug(f"New messages: {uuid_list!s}")
60
- except Exception as e:
61
- logger.debug(f"Exception handling command {message.module_path()}: {e!s}")
62
- raise e from e
63
-
64
- return messages
File without changes