Skip to content

Commit 19e05ca

Browse files
committed
simplify
1 parent ed28a00 commit 19e05ca

File tree

4 files changed

+67
-114
lines changed

4 files changed

+67
-114
lines changed

codeforlife/apps/__init__.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

codeforlife/servers/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""
2+
© Ocado Group
3+
Created on 28/03/2025 at 15:37:48(+00:00).
4+
"""
5+
6+
from .celery import CeleryServer
7+
from .django import DjangoServer

codeforlife/apps/celery.py renamed to codeforlife/servers/celery.py

Lines changed: 34 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
import atexit
99
import os
1010
import subprocess
11+
import sys
1112
import typing as t
1213

13-
from celery import Celery as _Celery
14+
import celery
1415

1516
from ..types import LogLevel
1617

18+
CELERY_MAIN_PATH = os.path.join(celery.__path__[0], "__main__.py")
1719

18-
class CeleryApplication(_Celery):
20+
21+
class CeleryServer(celery.Celery):
1922
"""A server for a Celery app."""
2023

2124
def __init__(
@@ -25,8 +28,18 @@ def __init__(
2528
):
2629
"""Initialize a Celery app.
2730
31+
Examples:
32+
```
33+
from codeforlife.servers import CeleryServer, DjangoServer
34+
35+
# Make sure to set up Django before initializing!
36+
DjangoServer.setup()
37+
38+
celery_app = CeleryServer().app
39+
```
40+
2841
Args:
29-
app: The Celery app name.
42+
app: The dot-path to the Celery app.
3043
debug: A flag designating whether to run the app in debug mode.
3144
3245
Raises:
@@ -39,8 +52,8 @@ def __init__(
3952
)
4053

4154
super().__init__()
42-
43-
self.app = app
55+
self.app = self
56+
self._app = app
4457

4558
# Using a string here means the worker doesn't have to serialize
4659
# the configuration object to child processes.
@@ -59,6 +72,10 @@ def _debug(self, *args, **kwargs):
5972

6073
print(f"Request: {self.request!r}")
6174

75+
if os.path.abspath(sys.argv[0]) != CELERY_MAIN_PATH:
76+
self.start_background_workers()
77+
self.start_background_beat()
78+
6279
def start_background_workers(
6380
self,
6481
workers: t.Optional[t.Union[t.Set[str], t.Dict[str, int]]] = None,
@@ -74,8 +91,6 @@ def start_background_workers(
7491
"""
7592
# pylint: enable=line-too-long
7693

77-
print("Starting all Celery workers.")
78-
7994
commands: t.Dict[str, t.List[str]] = {}
8095

8196
def build_command(worker: str, concurrency: int = 0):
@@ -84,7 +99,7 @@ def build_command(worker: str, concurrency: int = 0):
8499
"multi",
85100
"start",
86101
worker,
87-
f"--app={self.app}",
102+
f"--app={self._app}",
88103
f"--loglevel={log_level}",
89104
]
90105

@@ -106,20 +121,11 @@ def build_command(worker: str, concurrency: int = 0):
106121

107122
for worker, command in commands.items():
108123
try:
109-
process = subprocess.run(
110-
command,
111-
capture_output=True,
112-
text=True,
113-
check=True,
114-
)
115-
print(f"Successfully started Celery worker '{worker}'.")
116-
print(process.stdout)
117-
124+
subprocess.run(command, check=True)
118125
successfully_started_workers.add(worker)
119126

120-
except subprocess.CalledProcessError as e:
121-
print(f"Error starting Celery worker '{worker}': {e}")
122-
print(e.stderr)
127+
except Exception as ex: # pylint: disable=broad-exception-caught
128+
print(f"Error starting Celery worker '{worker}': {ex}")
123129

124130
if successfully_started_workers:
125131
atexit.register(
@@ -141,28 +147,21 @@ def stop_background_workers(
141147
log_level: The log level.
142148
"""
143149

144-
print("Stopping all Celery workers.")
145-
146150
try:
147-
process = subprocess.run(
151+
subprocess.run(
148152
[
149153
"celery",
150154
"multi",
151155
"stopwait",
152156
*workers,
153-
f"--app={self.app}",
157+
f"--app={self._app}",
154158
f"--loglevel={log_level}",
155159
],
156-
capture_output=True,
157-
text=True,
158160
check=True,
159161
)
160-
print("Successfully stopped all Celery workers.")
161-
print(process.stdout)
162162

163-
except subprocess.CalledProcessError as error:
164-
print(f"Error stopping all Celery workers: {error}")
165-
print(error.stderr)
163+
except Exception as ex: # pylint: disable=broad-exception-caught
164+
print(f"Error stopping all Celery workers: {ex}")
166165

167166
def start_background_beat(self, log_level: LogLevel = "INFO"):
168167
"""Start Celery beat using the 'celery --app=app beat' command.
@@ -174,64 +173,23 @@ def start_background_beat(self, log_level: LogLevel = "INFO"):
174173
The background process running Celery beat.
175174
"""
176175

177-
print("Starting Celery beat.")
178-
179176
try:
180177
process = subprocess.Popen( # pylint: disable=consider-using-with
181178
[
182179
"celery",
183-
f"--app={self.app}",
180+
f"--app={self._app}",
184181
"beat",
185182
f"--loglevel={log_level}",
186183
],
187184
stdout=subprocess.DEVNULL,
188185
stderr=subprocess.DEVNULL,
189186
)
190-
print("Successfully started Celery beat.")
191187

192-
atexit.register(self.stop_background_beat, process)
188+
atexit.register(process.terminate)
193189

194190
return process
195191

196-
except subprocess.CalledProcessError as error:
197-
print(f"Error starting Celery beat: {error}")
198-
199-
return None
200-
201-
def stop_background_beat(self, process: subprocess.Popen):
202-
"""Stop a Celery beat process.
203-
204-
Args:
205-
process: The process to stop.
206-
"""
207-
208-
print("Stopping Celery beat.")
209-
210-
try:
211-
process.terminate()
212-
print("Successfully stopped Celery beat.")
213-
214192
except Exception as ex: # pylint: disable=broad-exception-caught
215-
print(f"Error stopping Celery beat: {ex}")
216-
217-
def handle_startup(self):
218-
"""Handle the startup procedure of a Celery app.
219-
220-
Examples:
221-
```
222-
from codeforlife.apps import CeleryApplication, DjangoApplication
223-
224-
# Make sure to set up Django before starting!
225-
DjangoApplication.setup()
193+
print(f"Error starting Celery beat: {ex}")
226194

227-
celery_app = CeleryApplication().handle_startup()
228-
```
229-
230-
Returns:
231-
The Celery app instance for convenience.
232-
"""
233-
234-
self.start_background_workers()
235-
self.start_background_beat()
236-
237-
return self
195+
return None

codeforlife/apps/django.py renamed to codeforlife/servers/django.py

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import multiprocessing
77
import os
8+
import sys
89

910
from django import setup
1011
from django.core.asgi import get_asgi_application
@@ -14,20 +15,35 @@
1415

1516

1617
# pylint: disable-next=abstract-method
17-
class DjangoApplication(BaseApplication):
18+
class DjangoServer(BaseApplication):
1819
"""A server for a Django app.
1920
2021
Based off of:
2122
https://gist.github.com/Kludex/c98ed6b06f5c0f89fd78dd75ef58b424
2223
https://docs.gunicorn.org/en/stable/custom.html
2324
"""
2425

25-
def __init__(self, workers: int = int(os.getenv("WORKERS", "0"))):
26+
def __init__(
27+
self,
28+
app: str = "application",
29+
workers: int = int(os.getenv("WORKERS", "0")),
30+
):
2631
"""Initialize a Django app.
2732
2833
Before starting, all migrations will be applied.
2934
35+
Examples:
36+
```
37+
from codeforlife.apps import DjangoServer
38+
39+
# Make sure to set up Django before initializing!
40+
DjangoServer.setup()
41+
42+
django_app = DjangoServer().wsgi_app
43+
```
44+
3045
Args:
46+
app: The dot-path to the Django app.
3147
workers: The number of Gunicorn workers. 0 will auto-calculate.
3248
"""
3349

@@ -40,10 +56,16 @@ def __init__(self, workers: int = int(os.getenv("WORKERS", "0"))):
4056
"worker_class": "uvicorn.workers.UvicornWorker",
4157
}
4258

43-
self.application = get_asgi_application()
59+
self.asgi_app = get_asgi_application()
60+
self.wsgi_app = get_wsgi_application()
4461

4562
super().__init__()
4663

64+
# Auto-run if the entry script is the location of the Django app.
65+
file_name = os.path.basename(sys.argv[0])
66+
if os.path.splitext(file_name)[0] == app:
67+
self.run()
68+
4769
def load_config(self):
4870
config = {
4971
key: value
@@ -54,7 +76,7 @@ def load_config(self):
5476
self.cfg.set(key.lower(), value)
5577

5678
def load(self):
57-
return self.application
79+
return self.asgi_app
5880

5981
@staticmethod
6082
def setup(settings_module: str = "settings"):
@@ -67,30 +89,3 @@ def setup(settings_module: str = "settings"):
6789
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
6890

6991
setup()
70-
71-
def handle_startup(self, name: str):
72-
"""Handle the startup procedure of a Django app.
73-
74-
Examples:
75-
```
76-
from codeforlife.apps import DjangoApplication
77-
78-
# Make sure to set up Django before starting!
79-
DjangoApplication.setup()
80-
81-
django_app = DjangoApplication().handle_startup(__name__)
82-
```
83-
84-
Args:
85-
name: The name of the file calling this function.
86-
87-
Returns:
88-
An instance of a WSGI app.
89-
"""
90-
91-
if name == "__main__":
92-
self.run()
93-
else:
94-
return get_wsgi_application()
95-
96-
return None

0 commit comments

Comments
 (0)