csu 2.1.2__tar.gz → 2.1.4__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 (107) hide show
  1. {csu-2.1.2 → csu-2.1.4}/.bumpversion.cfg +1 -1
  2. {csu-2.1.2 → csu-2.1.4}/.cookiecutterrc +1 -1
  3. {csu-2.1.2 → csu-2.1.4}/PKG-INFO +3 -3
  4. {csu-2.1.2 → csu-2.1.4}/README.rst +2 -2
  5. {csu-2.1.2 → csu-2.1.4}/pyproject.toml +1 -1
  6. {csu-2.1.2 → csu-2.1.4}/src/csu/__init__.py +1 -1
  7. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/engine.py +6 -6
  8. {csu-2.1.2 → csu-2.1.4}/src/csu.egg-info/PKG-INFO +3 -3
  9. {csu-2.1.2 → csu-2.1.4}/src/csu.egg-info/SOURCES.txt +1 -0
  10. csu-2.1.4/tests/exampleworker.py +126 -0
  11. csu-2.1.4/tests/test_exampleworker.py +87 -0
  12. {csu-2.1.2 → csu-2.1.4}/tests/test_worker.py +0 -1
  13. {csu-2.1.2 → csu-2.1.4}/tox.ini +1 -0
  14. csu-2.1.2/tests/exampleworker.py +0 -104
  15. {csu-2.1.2 → csu-2.1.4}/.coveragerc +0 -0
  16. {csu-2.1.2 → csu-2.1.4}/.editorconfig +0 -0
  17. {csu-2.1.2 → csu-2.1.4}/.github/workflows/github-actions.yml +0 -0
  18. {csu-2.1.2 → csu-2.1.4}/.pre-commit-config.yaml +0 -0
  19. {csu-2.1.2 → csu-2.1.4}/.taplo.toml +0 -0
  20. {csu-2.1.2 → csu-2.1.4}/AUTHORS.rst +0 -0
  21. {csu-2.1.2 → csu-2.1.4}/CHANGELOG.rst +0 -0
  22. {csu-2.1.2 → csu-2.1.4}/CONTRIBUTING.rst +0 -0
  23. {csu-2.1.2 → csu-2.1.4}/LICENSE +0 -0
  24. {csu-2.1.2 → csu-2.1.4}/MANIFEST.in +0 -0
  25. {csu-2.1.2 → csu-2.1.4}/ci/bootstrap.py +0 -0
  26. {csu-2.1.2 → csu-2.1.4}/ci/requirements.txt +0 -0
  27. {csu-2.1.2 → csu-2.1.4}/ci/templates/.github/workflows/github-actions.yml +0 -0
  28. {csu-2.1.2 → csu-2.1.4}/pytest.ini +0 -0
  29. {csu-2.1.2 → csu-2.1.4}/setup.cfg +0 -0
  30. {csu-2.1.2 → csu-2.1.4}/src/csu/admin.py +0 -0
  31. {csu-2.1.2 → csu-2.1.4}/src/csu/conf.py +0 -0
  32. {csu-2.1.2 → csu-2.1.4}/src/csu/consts.py +0 -0
  33. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/__init__.py +0 -0
  34. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/auth.py +0 -0
  35. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/fields.py +0 -0
  36. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/forms.py +0 -0
  37. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/perms.py +0 -0
  38. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/phonenumber.py +0 -0
  39. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/serializers.py +0 -0
  40. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/test_auth.py +0 -0
  41. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/test_fields.py +0 -0
  42. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/test_forms.py +0 -0
  43. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/test_phonenumber.py +0 -0
  44. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/test_serializers.py +0 -0
  45. {csu-2.1.2 → csu-2.1.4}/src/csu/drf/views.py +0 -0
  46. {csu-2.1.2 → csu-2.1.4}/src/csu/enums.py +0 -0
  47. {csu-2.1.2 → csu-2.1.4}/src/csu/env.py +0 -0
  48. {csu-2.1.2 → csu-2.1.4}/src/csu/exceptions.py +0 -0
  49. {csu-2.1.2 → csu-2.1.4}/src/csu/export.py +0 -0
  50. {csu-2.1.2 → csu-2.1.4}/src/csu/fixups.py +0 -0
  51. {csu-2.1.2 → csu-2.1.4}/src/csu/forms/__init__.py +0 -0
  52. {csu-2.1.2 → csu-2.1.4}/src/csu/forms/crispy.py +0 -0
  53. {csu-2.1.2 → csu-2.1.4}/src/csu/forms/fields.py +0 -0
  54. {csu-2.1.2 → csu-2.1.4}/src/csu/gettext.py +0 -0
  55. {csu-2.1.2 → csu-2.1.4}/src/csu/gettext_lazy.py +0 -0
  56. {csu-2.1.2 → csu-2.1.4}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
  57. {csu-2.1.2 → csu-2.1.4}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
  58. {csu-2.1.2 → csu-2.1.4}/src/csu/logging.py +0 -0
  59. {csu-2.1.2 → csu-2.1.4}/src/csu/management.py +0 -0
  60. {csu-2.1.2 → csu-2.1.4}/src/csu/models.py +0 -0
  61. {csu-2.1.2 → csu-2.1.4}/src/csu/query.py +0 -0
  62. {csu-2.1.2 → csu-2.1.4}/src/csu/routers.py +0 -0
  63. {csu-2.1.2 → csu-2.1.4}/src/csu/service.py +0 -0
  64. {csu-2.1.2 → csu-2.1.4}/src/csu/templates/api_exception.html +0 -0
  65. {csu-2.1.2 → csu-2.1.4}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
  66. {csu-2.1.2 → csu-2.1.4}/src/csu/test_consts.py +0 -0
  67. {csu-2.1.2 → csu-2.1.4}/src/csu/test_env.py +0 -0
  68. {csu-2.1.2 → csu-2.1.4}/src/csu/test_exceptions.py +0 -0
  69. {csu-2.1.2 → csu-2.1.4}/src/csu/test_logging.py +0 -0
  70. {csu-2.1.2 → csu-2.1.4}/src/csu/test_management.py +0 -0
  71. {csu-2.1.2 → csu-2.1.4}/src/csu/test_service.py +0 -0
  72. {csu-2.1.2 → csu-2.1.4}/src/csu/test_timezones.py +0 -0
  73. {csu-2.1.2 → csu-2.1.4}/src/csu/test_utils.py +0 -0
  74. {csu-2.1.2 → csu-2.1.4}/src/csu/test_xml.py +0 -0
  75. {csu-2.1.2 → csu-2.1.4}/src/csu/timezones.py +0 -0
  76. {csu-2.1.2 → csu-2.1.4}/src/csu/utils.py +0 -0
  77. {csu-2.1.2 → csu-2.1.4}/src/csu/views.py +0 -0
  78. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/__init__.py +0 -0
  79. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/admin.py +0 -0
  80. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/asgi.py +0 -0
  81. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/enums.py +0 -0
  82. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/job.py +0 -0
  83. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/models.py +0 -0
  84. {csu-2.1.2 → csu-2.1.4}/src/csu/worker/registry.py +0 -0
  85. {csu-2.1.2 → csu-2.1.4}/src/csu/wsgi.py +0 -0
  86. {csu-2.1.2 → csu-2.1.4}/src/csu/xml.py +0 -0
  87. {csu-2.1.2 → csu-2.1.4}/src/csu.egg-info/dependency_links.txt +0 -0
  88. {csu-2.1.2 → csu-2.1.4}/src/csu.egg-info/requires.txt +0 -0
  89. {csu-2.1.2 → csu-2.1.4}/src/csu.egg-info/top_level.txt +0 -0
  90. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
  91. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
  92. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service/test_decoding.yaml +0 -0
  93. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service/test_error.yaml +0 -0
  94. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service/test_logging.yaml +0 -0
  95. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service/test_redirects.yaml +0 -0
  96. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
  97. {csu-2.1.2 → csu-2.1.4}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
  98. {csu-2.1.2 → csu-2.1.4}/tests/conftest.py +0 -0
  99. {csu-2.1.2 → csu-2.1.4}/tests/test_models.py +0 -0
  100. {csu-2.1.2 → csu-2.1.4}/tests/test_service.py +0 -0
  101. {csu-2.1.2 → csu-2.1.4}/tests/test_service_auth.py +0 -0
  102. {csu-2.1.2 → csu-2.1.4}/tests/test_views.py +0 -0
  103. {csu-2.1.2 → csu-2.1.4}/tests/testproject/__init__.py +0 -0
  104. {csu-2.1.2 → csu-2.1.4}/tests/testproject/fixtures/testuser.json +0 -0
  105. {csu-2.1.2 → csu-2.1.4}/tests/testproject/models.py +0 -0
  106. {csu-2.1.2 → csu-2.1.4}/tests/testproject/settings.py +0 -0
  107. {csu-2.1.2 → csu-2.1.4}/tests/testproject/urls.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 2.1.2
2
+ current_version = 2.1.4
3
3
  commit = True
4
4
  tag = True
5
5
 
@@ -40,7 +40,7 @@ default_context:
40
40
  sphinx_theme: furo
41
41
  test_matrix_separate_coverage: 'no'
42
42
  tests_inside_package: 'yes'
43
- version: 2.1.2
43
+ version: 2.1.4
44
44
  version_manager: bump2version
45
45
  website: https://blog.ionelmc.ro
46
46
  year_from: '2024'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csu
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Clean Slate Utils - bunch of utility code, mostly Django/DRF specific.
5
5
  Author-email: Ionel Cristian Mărieș <contact@ionelmc.ro>
6
6
  License-Expression: BSD-2-Clause
@@ -77,9 +77,9 @@ Overview
77
77
  .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
78
78
  :alt: Supported implementations
79
79
  :target: https://pypi.org/project/csu
80
- .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.1.2.svg
80
+ .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.1.4.svg
81
81
  :alt: Commits since latest release
82
- :target: https://github.com/ionelmc/python-csu/compare/v2.1.2...main
82
+ :target: https://github.com/ionelmc/python-csu/compare/v2.1.4...main
83
83
  .. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
84
84
  :alt: Scrutinizer Status
85
85
  :target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
@@ -34,9 +34,9 @@ Overview
34
34
  .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
35
35
  :alt: Supported implementations
36
36
  :target: https://pypi.org/project/csu
37
- .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.1.2.svg
37
+ .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.1.4.svg
38
38
  :alt: Commits since latest release
39
- :target: https://github.com/ionelmc/python-csu/compare/v2.1.2...main
39
+ :target: https://github.com/ionelmc/python-csu/compare/v2.1.4...main
40
40
  .. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
41
41
  :alt: Scrutinizer Status
42
42
  :target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
@@ -12,7 +12,7 @@ dynamic = [
12
12
  "readme",
13
13
  ]
14
14
  name = "csu"
15
- version = "2.1.2"
15
+ version = "2.1.4"
16
16
  license = "BSD-2-Clause"
17
17
  license-files = ["LICENSE"]
18
18
  description = "Clean Slate Utils - bunch of utility code, mostly Django/DRF specific."
@@ -2,4 +2,4 @@
2
2
  a.k.a Clean Slate Utils
3
3
  """
4
4
 
5
- __version__ = "2.1.2"
5
+ __version__ = "2.1.4"
@@ -293,19 +293,19 @@ class AbstractEngine(ABC):
293
293
 
294
294
  async def push(self, job: Job | None) -> None:
295
295
  logger.info("%s.push(%s)", self, job)
296
+ self.queue_awaited.clear() # clear it here, it's the shortest path to "consumer wakes up and calls .set()"
296
297
  await self.queue.put(job)
297
298
 
298
299
  @asynccontextmanager
299
300
  async def consume(self) -> AsyncIterator[Job | None]:
300
- try:
301
- self.queue_awaited.set()
302
- yield await self.queue.get()
303
- finally:
304
- self.queue.task_done()
301
+ self.queue_awaited.set()
302
+ yield await self.queue.get()
303
+ # if raised then there's no task to mark as done
304
+ self.queue.task_done()
305
305
 
306
306
  async def wait_worker_ready(self):
307
+ # event set after consuming, cleared right before there's a task
307
308
  await self.queue_awaited.wait()
308
- self.queue_awaited.clear()
309
309
 
310
310
  def add_worker(self, worker: AbstractWorker):
311
311
  assert self.queue is None, f"{self} is already started"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csu
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Clean Slate Utils - bunch of utility code, mostly Django/DRF specific.
5
5
  Author-email: Ionel Cristian Mărieș <contact@ionelmc.ro>
6
6
  License-Expression: BSD-2-Clause
@@ -77,9 +77,9 @@ Overview
77
77
  .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
78
78
  :alt: Supported implementations
79
79
  :target: https://pypi.org/project/csu
80
- .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.1.2.svg
80
+ .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.1.4.svg
81
81
  :alt: Commits since latest release
82
- :target: https://github.com/ionelmc/python-csu/compare/v2.1.2...main
82
+ :target: https://github.com/ionelmc/python-csu/compare/v2.1.4...main
83
83
  .. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
84
84
  :alt: Scrutinizer Status
85
85
  :target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
@@ -83,6 +83,7 @@ src/csu/worker/models.py
83
83
  src/csu/worker/registry.py
84
84
  tests/conftest.py
85
85
  tests/exampleworker.py
86
+ tests/test_exampleworker.py
86
87
  tests/test_models.py
87
88
  tests/test_service.py
88
89
  tests/test_service_auth.py
@@ -0,0 +1,126 @@
1
+ import asyncio
2
+ import logging
3
+ import sys
4
+ from dataclasses import asdict
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from logging import getLogger
8
+
9
+ from csu.worker.engine import AbstractConsumer
10
+ from csu.worker.engine import AbstractEngine
11
+ from csu.worker.engine import AbstractProducer
12
+ from csu.worker.engine import NoPrefetchingProducerMixin
13
+ from csu.worker.engine import SleepingCooldown
14
+ from csu.worker.engine import SleepingCooldownMixin
15
+ from csu.worker.engine import TaskProtocol
16
+ from csu.worker.job import Job
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class NoOpTask:
23
+ """
24
+ Simple task model that doesn't store anything.
25
+ """
26
+
27
+ id: int
28
+
29
+ @property
30
+ def job_kwargs(self):
31
+ return asdict(self)
32
+
33
+ async def done_watchdog(self, job: Job):
34
+ """
35
+ Nothing done as there's no storage for this job.
36
+ """
37
+
38
+
39
+ @dataclass
40
+ class ExampleJob[RequestStruct](Job):
41
+ id: int
42
+
43
+
44
+ class ExampleResultType(StrEnum):
45
+ PENDING = "PENDING"
46
+ SUCCESS = "SUCCESS"
47
+ ERROR = "ERROR"
48
+ ABSENT = "ABSENT"
49
+
50
+
51
+ if sys.argv[1] == "prefetch":
52
+
53
+ class NoPrefetchingProducerMixin:
54
+ pass
55
+
56
+
57
+ class ExampleProducer(
58
+ NoPrefetchingProducerMixin,
59
+ SleepingCooldownMixin,
60
+ AbstractProducer,
61
+ ):
62
+ engine: ExampleEngine
63
+ job_class = ExampleJob
64
+ counter = 0
65
+ cooldown_min_seconds = cooldown_max_seconds = 1
66
+ cooldown_on = SleepingCooldown.IDLE
67
+
68
+ async def fetch_task(self) -> TaskProtocol | None:
69
+ self.counter += 1
70
+ if self.counter > self.engine.max_jobs:
71
+ print(f"PRODUCER fetch_task @ {self.counter} EXHAUSTED")
72
+ return
73
+ if not self.engine.producer_skip or self.counter % self.engine.producer_skip:
74
+ print(f"PRODUCER fetch_task @ {self.counter} RETURN")
75
+ return NoOpTask(self.counter)
76
+ else:
77
+ print(f"PRODUCER fetch_task @ {self.counter} SKIP")
78
+
79
+
80
+ class ExampleConsumer(AbstractConsumer):
81
+ result_type_enum = ExampleResultType
82
+ engine: ExampleEngine
83
+
84
+ def __init__(self, engine: ExampleEngine, name):
85
+ super().__init__(engine)
86
+ self.name = name
87
+
88
+ async def perform_work(self, job: ExampleJob):
89
+ print(f"CONSUMER {self.name} perform_work JOB {job.id} START")
90
+ try:
91
+ await asyncio.sleep(self.engine.consumer_sleep)
92
+ return {"foo": "bar"}
93
+ finally:
94
+ print(f"CONSUMER {self.name} perform_work JOB {job.id} DONE")
95
+
96
+ async def save_result(self, job: ExampleJob, /, **kwargs):
97
+ print(f"CONSUMER {self.name} save_result JOB {job.id}")
98
+
99
+
100
+ class ExampleEngine(AbstractEngine):
101
+ def __init__(self, consumers, producer_skip, consumer_sleep, max_jobs):
102
+ super().__init__()
103
+ self.consumers = int(consumers)
104
+ self.producer_skip = int(producer_skip)
105
+ self.consumer_sleep = int(consumer_sleep) / 1000
106
+ self.max_jobs = int(max_jobs)
107
+ print(self.__dict__)
108
+
109
+ async def start(self):
110
+ for i in range(self.consumers):
111
+ self.add_worker(ExampleConsumer(engine=self, name=i))
112
+
113
+ self.add_worker(ExampleProducer(engine=self))
114
+
115
+ await super().start()
116
+
117
+ while self.is_work_pending():
118
+ await asyncio.sleep(1)
119
+ print(self.inspect())
120
+ print(f"DONE: {self.inspect()}")
121
+
122
+
123
+ if __name__ == "__main__":
124
+ logging.basicConfig(level=logging.DEBUG)
125
+ engine = ExampleEngine(*sys.argv[2:])
126
+ asyncio.run(engine.start())
@@ -0,0 +1,87 @@
1
+ import sys
2
+
3
+ from process_tests import TestProcess
4
+ from process_tests import dump_on_error
5
+ from process_tests import wait_for_strings
6
+
7
+
8
+ def test_noprefetch_noskip():
9
+ with TestProcess(sys.executable, "-mexampleworker", "nope", "3", "0", "1000", "4") as target, dump_on_error(target.read):
10
+ wait_for_strings(
11
+ target.read,
12
+ 5,
13
+ "PRODUCER fetch_task @ 1 RETURN",
14
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
15
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
16
+ "working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
17
+ "- performing...",
18
+ "CONSUMER 0 perform_work JOB 1 START",
19
+ "CONSUMER 0 perform_work JOB 1 DONE",
20
+ "CONSUMER 0 save_result JOB 1",
21
+ "PRODUCER fetch_task @ 2 RETURN",
22
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=2))",
23
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=2).done of NoOpTask(id=2).",
24
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=2)",
25
+ ").run() - performing...",
26
+ "CONSUMER 1 perform_work JOB 2 START",
27
+ "CONSUMER 1 perform_work JOB 2 DONE",
28
+ "CONSUMER 1 save_result JOB 2",
29
+ "PRODUCER fetch_task @ 3 RETURN",
30
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
31
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
32
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
33
+ ").run() - performing...",
34
+ "CONSUMER 2 perform_work JOB 3 START",
35
+ "CONSUMER 2 perform_work JOB 3 DONE",
36
+ "CONSUMER 2 save_result JOB 3",
37
+ "PRODUCER fetch_task @ 4 RETURN",
38
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=4))",
39
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=4).done of NoOpTask(id=4).",
40
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=4)",
41
+ ").run() - performing...",
42
+ "CONSUMER 0 perform_work JOB 4 START",
43
+ "CONSUMER 0 perform_work JOB 4 DONE",
44
+ "CONSUMER 0 save_result JOB 4",
45
+ "EXHAUSTED",
46
+ )
47
+
48
+
49
+ def test_prefetch_skip():
50
+ with TestProcess(sys.executable, "-mexampleworker", "nope", "3", "0", "100", "4") as target, dump_on_error(target.read):
51
+ wait_for_strings(
52
+ target.read,
53
+ 5,
54
+ "PRODUCER fetch_task @ 1 RETURN",
55
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=1))",
56
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=1).done of NoOpTask(id=1).",
57
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=1)",
58
+ ").run() - performing...",
59
+ "CONSUMER 0 perform_work JOB 1 START",
60
+ "CONSUMER 0 perform_work JOB 1 DONE",
61
+ "CONSUMER 0 save_result JOB 1",
62
+ "PRODUCER fetch_task @ 2 RETURN",
63
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=2))",
64
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=2).done of NoOpTask(id=2).",
65
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=2)",
66
+ ").run() - performing...",
67
+ "CONSUMER 1 perform_work JOB 2 START",
68
+ "CONSUMER 1 perform_work JOB 2 DONE",
69
+ "CONSUMER 1 save_result JOB 2",
70
+ "PRODUCER fetch_task @ 3 RETURN",
71
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=3))",
72
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=3).done of NoOpTask(id=3).",
73
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=3)",
74
+ ").run() - performing...",
75
+ "CONSUMER 2 perform_work JOB 3 START",
76
+ "CONSUMER 2 perform_work JOB 3 DONE",
77
+ "CONSUMER 2 save_result JOB 3",
78
+ "PRODUCER fetch_task @ 4 RETURN",
79
+ "INFO:worker:ExampleEngine(4w/0q).push(ExampleJob(done=<Future pending>, started=<Future pending>, id=4))",
80
+ "INFO:worker:ExampleProducer().run() awaiting ExampleJob(done=<Future pending>, started=<Future pending>, id=4).done of NoOpTask(id=4).",
81
+ ").run() working on: ExampleJob(done=<Future pending>, started=<Future finished result=True>, id=4)",
82
+ ").run() - performing...",
83
+ "CONSUMER 0 perform_work JOB 4 START",
84
+ "CONSUMER 0 perform_work JOB 4 DONE",
85
+ "CONSUMER 0 save_result JOB 4",
86
+ "PRODUCER fetch_task @ 5 EXHAUSTED",
87
+ )
@@ -42,7 +42,6 @@ class Consumer(AbstractConsumer):
42
42
  class Engine(AbstractEngine):
43
43
  async def start(self):
44
44
  worker = Consumer(engine=self)
45
- worker.start()
46
45
  self.add_worker(worker)
47
46
  await super().start()
48
47
  logging.info("engine started")
@@ -35,6 +35,7 @@ deps =
35
35
  pytest-django
36
36
  pytest-httpbin
37
37
  pytest-recording
38
+ process-tests
38
39
  inline-snapshot
39
40
  time-machine
40
41
  hunter
@@ -1,104 +0,0 @@
1
- import asyncio
2
- import logging
3
- from dataclasses import asdict
4
- from dataclasses import dataclass
5
- from enum import StrEnum
6
- from logging import getLogger
7
-
8
- from csu.worker.engine import AbstractConsumer
9
- from csu.worker.engine import AbstractEngine
10
- from csu.worker.engine import AbstractProducer
11
- from csu.worker.engine import TaskProtocol
12
- from csu.worker.job import Job
13
-
14
- logger = getLogger(__name__)
15
-
16
-
17
- @dataclass
18
- class NoOpTask:
19
- """
20
- Simple task model that doesn't store anything.
21
- """
22
-
23
- id: int
24
-
25
- @property
26
- def job_kwargs(self):
27
- return asdict(self)
28
-
29
- async def done_watchdog(self, job: Job):
30
- """
31
- Nothing done as there's no storage for this job.
32
- """
33
-
34
-
35
- @dataclass
36
- class ExampleJob[RequestStruct](Job):
37
- id: int
38
-
39
-
40
- class ExampleResultType(StrEnum):
41
- PENDING = "PENDING"
42
- SUCCESS = "SUCCESS"
43
- ERROR = "ERROR"
44
- ABSENT = "ABSENT"
45
-
46
-
47
- class ExampleProducer(
48
- # NoPrefetchingProducerMixin,
49
- AbstractProducer,
50
- ):
51
- engine: ExampleEngine
52
- job_class = ExampleJob
53
- counter = 0
54
-
55
- async def fetch_task(self) -> TaskProtocol | None:
56
- self.counter += 1
57
- logger.info("%s.fetch_task() -> %s", self, self.counter)
58
- return NoOpTask(self.counter)
59
-
60
-
61
- class ExampleConsumer(AbstractConsumer):
62
- result_type_enum = ExampleResultType
63
- engine: ExampleEngine
64
-
65
- async def perform_work(self, job: ExampleJob):
66
- logger.info("%s.perform_work(%s)", self, job)
67
- await asyncio.sleep(1)
68
- return {"foo": "bar"}
69
-
70
- async def save_result(self, job: ExampleJob, /, **kwargs):
71
- logger.info("%s.save_result(%s)", self, job)
72
-
73
-
74
- WORKERS = 5
75
-
76
-
77
- class ExampleEngine(AbstractEngine):
78
- queue_maxsize = WORKERS
79
-
80
- async def start(self):
81
- logger.info("Starting workers...")
82
-
83
- for _ in range(WORKERS):
84
- consumer = ExampleConsumer(engine=self)
85
- consumer.start()
86
- self.add_worker(consumer)
87
-
88
- producer = ExampleProducer(engine=self)
89
- producer.start()
90
- self.add_worker(producer)
91
-
92
- await super().start()
93
-
94
- while self.is_work_pending():
95
- await asyncio.sleep(1)
96
- logger.info("%s", self.inspect())
97
- logger.info("DONE: %s", self.inspect())
98
-
99
-
100
- if __name__ == "__main__":
101
- logging.basicConfig(level=logging.DEBUG)
102
- engine = ExampleEngine()
103
- print(engine)
104
- asyncio.run(engine.start())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes