Skip to content

Commit 824f112

Browse files
authored
New Example: examples/hybrid_setup (#246)
1 parent e75697a commit 824f112

File tree

19 files changed

+649
-7
lines changed

19 files changed

+649
-7
lines changed

.github/workflows/examples.yml

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
working-directory: examples/myworker
6262
timeout-minutes: 10
6363
run: |
64-
pytest -vv tests
64+
pytest -xsv tests
6565
6666
range:
6767
runs-on: ${{ matrix.os }}
@@ -94,7 +94,7 @@ jobs:
9494
working-directory: examples/range
9595
timeout-minutes: 30
9696
run: |
97-
pytest -vv tests
97+
pytest -xsv tests
9898
9999
rabbitmq_management:
100100
runs-on: ${{ matrix.os }}
@@ -127,7 +127,7 @@ jobs:
127127
working-directory: examples/rabbitmq_management
128128
timeout-minutes: 10
129129
run: |
130-
pytest -vv tests
130+
pytest -xsv tests
131131
132132
django:
133133
runs-on: ${{ matrix.os }}
@@ -166,7 +166,7 @@ jobs:
166166
timeout-minutes: 10
167167
run: |
168168
export DJANGO_SETTINGS_MODULE=proj.settings
169-
pytest -vv tests
169+
pytest -xsv tests
170170
171171
myutils:
172172
runs-on: ${{ matrix.os }}
@@ -199,7 +199,7 @@ jobs:
199199
working-directory: examples/myutils
200200
timeout-minutes: 10
201201
run: |
202-
pytest -vv tests
202+
pytest -xsv tests
203203
204204
worker_pool:
205205
runs-on: ${{ matrix.os }}
@@ -232,4 +232,37 @@ jobs:
232232
working-directory: examples/worker_pool
233233
timeout-minutes: 10
234234
run: |
235-
pytest -vv tests
235+
pytest -xsv tests
236+
237+
hybrid_setup:
238+
runs-on: ${{ matrix.os }}
239+
240+
strategy:
241+
fail-fast: false
242+
matrix:
243+
python-version: ["3.12"]
244+
os: ["ubuntu-latest"]
245+
246+
steps:
247+
- name: Install apt packages
248+
if: startsWith(matrix.os, 'ubuntu-')
249+
run: |
250+
sudo apt update
251+
- uses: actions/checkout@v4
252+
- name: Set up Python ${{ matrix.python-version }}
253+
uses: actions/setup-python@v5
254+
with:
255+
python-version: ${{ matrix.python-version }}
256+
cache: 'pip'
257+
cache-dependency-path: '**/setup.py'
258+
- name: Install dependencies
259+
working-directory: examples/hybrid_setup
260+
run: |
261+
python -m pip install --upgrade pip
262+
pip install -r requirements.txt
263+
264+
- name: Run tests
265+
working-directory: examples/hybrid_setup
266+
timeout-minutes: 10
267+
run: |
268+
pytest -xsv tests
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
.. _examples_hybrid_setup:
2+
3+
==============
4+
hybrid_setup
5+
==============
6+
7+
:Release: |version|
8+
:Date: |today|
9+
10+
.. contents::
11+
:local:
12+
:depth: 2
13+
14+
Description
15+
===========
16+
17+
The purpose of this example is to demonstrate a more complex setup with multiple components.
18+
The example is using two brokers, with the failover feature, a backend and multiple workers of different pools and versions.
19+
One of the workers is using gevent with the latest Celery release on the default queue,
20+
while the other is using prefork with Celery 4 and its own queue.
21+
22+
It uses the following workflow to utilize both workers:
23+
24+
.. code-block:: python
25+
26+
canvas = (
27+
group(
28+
identity.si("Hello, "),
29+
identity.si("world!"),
30+
)
31+
| noop.s().set(queue="legacy")
32+
| identity.si("Done!")
33+
)
34+
35+
Highlights
36+
~~~~~~~~~~
37+
38+
1. No default components.
39+
2. Session broker and backend components.
40+
- Shared between tests, but not between :pypi:`pytest-xdist` sessions.
41+
- Only the workers are created again for each test case.
42+
3. Injects tasks and signal handlers modules to all workers.
43+
44+
This example is based on,
45+
46+
- The :ref:`examples_myworker` example.
47+
- The :ref:`examples_worker_pool` example.
48+
49+
Breakdown
50+
=========
51+
52+
File Structure
53+
~~~~~~~~~~~~~~
54+
55+
The following diagram lists the relevant files in the project.
56+
57+
.. code-block:: text
58+
59+
hybrid_setup/
60+
├── requirements.txt
61+
└── tests/
62+
├── conftest.py
63+
├── test_hybrid_setup.py
64+
└── vendors/
65+
├── __init__.py
66+
├── memcached.py
67+
├── rabbitmq.py
68+
└── workers/
69+
├── __init__.py
70+
├── gevent.Dockerfile
71+
├── gevent.py
72+
├── legacy.Dockerfile
73+
├── legacy.py
74+
├── signals.py
75+
└── tasks.py
76+
77+
requirements.txt
78+
~~~~~~~~~~~~~~~~
79+
80+
Take a look at the requirements file for this example:
81+
82+
.. literalinclude:: ../../../examples/hybrid_setup/requirements.txt
83+
:language: text
84+
:caption: examples.hybrid_setup.requirements.txt
85+
86+
Take note the :pypi:`gevent` can be installed independently from the :pypi:`celery` package.
87+
88+
conftest.py
89+
~~~~~~~~~~~
90+
91+
The ``conftest.py`` file will be used to aggregate each individual configuration. To understand how it works,
92+
we'll split the file into three parts.
93+
94+
1. Creating the docker network for the components.
95+
2. Configuring the broker, backend and workers for the setup.
96+
3. Injecting the tasks and signal handlers modules.
97+
98+
.. literalinclude:: ../../../examples/hybrid_setup/tests/conftest.py
99+
:language: python
100+
:caption: examples.hybrid_setup.tests.conftest.py
101+
:start-after: # ----------------------------
102+
103+
test_hybrid_setup.py
104+
~~~~~~~~~~~~~~~~~~~~
105+
106+
Every test case that uses the :func:`celery_setup <pytest_celery.fixtures.setup.celery_setup>` fixture will run
107+
its scenario on the setup that was configured in the ``conftest.py`` file.
108+
109+
For this example, we have the following test cases.
110+
111+
.. literalinclude:: ../../../examples/hybrid_setup/tests/test_hybrid_setup.py
112+
:language: python
113+
:caption: examples.hybrid_setup.tests.test_hybrid_setup.py
114+
:start-after: TestHybridSetupExample
115+
116+
.. tip::
117+
118+
The components themselves can be used in the test case to easily access their attributes and methods, like shown
119+
in the failover test case. When used without the :func:`celery_setup <pytest_celery.fixtures.setup.celery_setup>`
120+
fixture, the components will run independently and might not be aware of each other.
121+
122+
rabbitmq.py and memcached.py
123+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
124+
125+
The brokers and result backend are defined as independent components that are being configured into the setup
126+
using the ``conftest.py`` file. They add **session scope** fixtures and integrate using the matching :ref:`node class <test-nodes>`.
127+
128+
Main | Failover Brokers
129+
-----------------------
130+
131+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/rabbitmq.py
132+
:language: python
133+
:caption: examples.hybrid_setup.tests.vendors.rabbitmq.py
134+
135+
Result Backend
136+
--------------
137+
138+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/memcached.py
139+
:language: python
140+
:caption: examples.hybrid_setup.tests.vendors.memcached.py
141+
142+
gevent.py and gevent.Dockerfile
143+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144+
145+
These files are taken from the :ref:`test_gevent_pool` example with one simple change.
146+
147+
.. code-block:: python
148+
149+
RUN pip install "celery[gevent]" "pytest-celery[all]==1.0.0b4"
150+
151+
The Dockerfile doesn't use the requirements file, but instead installs the packages directly.
152+
153+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/workers/gevent.Dockerfile
154+
:language: docker
155+
:caption: examples.hybrid_setup.tests.vendors.workers.gevent.Dockerfile
156+
157+
.. note::
158+
159+
The :ref:`test_gevent_pool` example defines everything in the test file. Here we use the ``gevent.py`` file.
160+
161+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/workers/gevent.py
162+
:language: python
163+
:caption: examples.hybrid_setup.tests.vendors.workers.gevent.py
164+
165+
legacy.py and legacy.Dockerfile
166+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
167+
168+
The "legacy" worker is basically Celery 4 worker with the prefork pool. Very similar to the gevent worker,
169+
we add a new Dockerfile and worker module.
170+
171+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/workers/legacy.Dockerfile
172+
:language: docker
173+
:caption: examples.hybrid_setup.tests.vendors.workers.legacy.Dockerfile
174+
175+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/workers/legacy.py
176+
:language: python
177+
:caption: examples.hybrid_setup.tests.vendors.workers.legacy.py
178+
179+
.. tip::
180+
181+
Check all of the configurations above again and notice the usage of ``hybrid_setup_example_network``.
182+
See how both session and non-session fixtures are sharing the same session docker network.
183+
184+
tasks.py and signals.py
185+
~~~~~~~~~~~~~~~~~~~~~~~
186+
187+
The tasks and signal handlers are being injected into the workers using the ``conftest.py`` file,
188+
according to the documentation:
189+
190+
1. :ref:`injecting-tasks`.
191+
2. :ref:`injecting-signals-handlers`.
192+
193+
The files themselves are very simple,
194+
195+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/workers/tasks.py
196+
:language: python
197+
:caption: examples.hybrid_setup.tests.vendors.workers.tasks.py
198+
199+
.. literalinclude:: ../../../examples/hybrid_setup/tests/vendors/workers/signals.py
200+
:language: python
201+
:caption: examples.hybrid_setup.tests.vendors.workers.signals.py
202+
203+
And again, from ``confest.py``,
204+
205+
.. code-block:: python
206+
207+
@pytest.fixture
208+
def default_worker_tasks(default_worker_tasks: set) -> set:
209+
from tests.vendors.workers import tasks
210+
211+
default_worker_tasks.add(tasks)
212+
return default_worker_tasks
213+
214+
215+
@pytest.fixture
216+
def default_worker_signals(default_worker_signals: set) -> set:
217+
from tests.vendors.workers import signals
218+
219+
default_worker_signals.add(signals)
220+
return default_worker_signals
221+
222+
.. note::
223+
224+
The tasks and signals are being injected into the workers that use the default volume with:
225+
226+
.. code-block:: python
227+
228+
volumes={"{default_worker_volume.name}": defaults.DEFAULT_WORKER_VOLUME},
229+
230+
Both our workers are using the default volume, so we only need to inject the tasks and signals once.

docs/userguide/examples/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ Every example is an independent project and is tested via the
2222
range
2323
myutils
2424
django
25+
hybrid_setup

docs/userguide/examples/worker_pool.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The purpose of this worker is to ensure the gevent dependency is installed.
5353
:caption: examples.worker_pool.Dockerfile
5454

5555
.. literalinclude:: ../../../examples/worker_pool/requirements.txt
56-
:language: docker
56+
:language: text
5757
:caption: examples.worker_pool.requirements.txt
5858

5959
tasks.py
@@ -65,6 +65,8 @@ Our tasks module is using the example task from the `Celery gevent example <http
6565
:language: python
6666
:caption: examples.worker_pool.tasks.py
6767

68+
.. _test_gevent_pool:
69+
6870
test_gevent_pool.py
6971
~~~~~~~~~~~~~~~~~~~
7072

examples/hybrid_setup/pytest.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[pytest]
2+
log_cli = true
3+
log_cli_level = INFO
4+
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
5+
log_cli_date_format = %Y-%m-%d %H:%M:%S
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pytest>=7.4.4
2+
pytest-xdist>=3.5.0
3+
pytest-subtests>=0.11.0
4+
celery[gevent]
5+
pytest-celery[all]@git+https://github.com/celery/pytest-celery.git

examples/hybrid_setup/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)