diff --git a/.env.example b/.env.example index 1f050d4a..2d47ad1f 100644 --- a/.env.example +++ b/.env.example @@ -13,5 +13,5 @@ CACHE_URL=redis://127.0.0.1:${REDIS_PORT}/0 # Used to select which other services to run alongside # manage.py, pytest and runserver -PROCFILE_FULL=Procfile.full -PROCFILE_MINIMAL=Procfile.minimal +PROCFILE_STATIC=Procfile.static +PROCFILE_SERVICE=Procfile.service diff --git a/Procfile.full b/Procfile.full deleted file mode 100644 index 31652ef9..00000000 --- a/Procfile.full +++ /dev/null @@ -1,2 +0,0 @@ -bundler: npm run serve -redis: redis-server --port $REDIS_PORT \ No newline at end of file diff --git a/Procfile.minimal b/Procfile.service similarity index 100% rename from Procfile.minimal rename to Procfile.service diff --git a/Procfile.static b/Procfile.static new file mode 100644 index 00000000..857b2f2d --- /dev/null +++ b/Procfile.static @@ -0,0 +1 @@ +bundler: npm run serve \ No newline at end of file diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index b5630d2b..2da2fc42 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -88,15 +88,15 @@ tous ces services sont déjà configurés. Pour désactiver Honcho il suffit de ne sélectionner aucun `PROCFILE_` dans la config. ```dotenv -PROCFILE_FULL= -PROCFILE_MINIMAL= +PROCFILE_STATIC= +PROCFILE_SERVICE= ``` !!! note - Si honcho ne tourne plus, la recompilation automatique + Si `PROCFILE_STATIC` est désactivé, la recompilation automatique des fichiers statiques ne se fait plus. - Si vous en avez besoin et que vous travaillez sans honcho, + Si vous en avez besoin et que vous travaillez sans `PROCFILE_STATIC`, vous devez ouvrir une autre fenêtre de votre terminal et lancer la commande `npm run serve` diff --git a/docs/tutorial/structure.md b/docs/tutorial/structure.md index 6af76354..7c740bde 100644 --- a/docs/tutorial/structure.md +++ b/docs/tutorial/structure.md @@ -82,8 +82,8 @@ sith/ ├── pyproject.toml (31) ├── .venv/ (32) ├── .python-version (33) -├── Procfile.full (34) -├── Procfile.minimal (35) +├── Procfile.static (34) +├── Procfile.service (35) └── README.md ``` @@ -142,10 +142,10 @@ sith/ de certaines d'entre elles. 32. Dossier d'environnement virtuel généré par uv 33. Fichier qui contrôle quelle version de python utiliser pour le projet -34. Fichier qui contrôle les services additionnels à lancer - avec le serveur de développement. Serveurs + gestion des statics. -35. Fichier qui contrôle les services additionnels à lancer - avec les autres commandes (pytest, shell…). Serveurs uniquement. +34. Fichier qui contrôle les commandes à lancer pour gérer la compilation + automatique des static et autres services nécessaires à la command runserver. +35. Fichier qui contrôle les services tiers nécessaires au fonctionnement + du Sith tel que redis. ## L'application principale diff --git a/manage.py b/manage.py index cb26d55d..101696e2 100755 --- a/manage.py +++ b/manage.py @@ -21,7 +21,7 @@ import sys from django.utils.autoreload import DJANGO_AUTORELOAD_ENV from sith.composer import start_composer, stop_composer -from sith.settings import PROCFILE_FULL, PROCFILE_MINIMAL +from sith.settings import PROCFILE_SERVICE if __name__ == "__main__": logging.basicConfig(encoding="utf-8", level=logging.INFO) @@ -30,9 +30,8 @@ if __name__ == "__main__": from django.core.management import execute_from_command_line - procfile = PROCFILE_FULL if sys.argv[1] == "runserver" else PROCFILE_MINIMAL - if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and procfile is not None: - start_composer(procfile) - _ = atexit.register(stop_composer) + if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and PROCFILE_SERVICE is not None: + start_composer(PROCFILE_SERVICE) + _ = atexit.register(stop_composer, procfile=PROCFILE_SERVICE) execute_from_command_line(sys.argv) diff --git a/sith/composer.py b/sith/composer.py index b1d90d92..dd3d648a 100644 --- a/sith/composer.py +++ b/sith/composer.py @@ -9,30 +9,37 @@ import psutil from sith import settings -def get_pid() -> int | None: +def get_pid_file(procfile: Path) -> Path: + """Get the PID file associated with a procfile""" + return settings.BASE_DIR / procfile.with_suffix(f"{procfile.suffix}.pid") + + +def get_pid(procfile: Path) -> int | None: """Read the PID file to get the currently running composer if it exists""" - if not settings.COMPOSER_PID_PATH.exists(): + file = get_pid_file(procfile) + if not file.exists(): return None - with open(settings.COMPOSER_PID_PATH, "r", encoding="utf8") as f: + with open(file, "r", encoding="utf8") as f: return int(f.read()) -def write_pid(pid: int): +def write_pid(procfile: Path, pid: int): """Write currently running composer pid in PID file""" - if not settings.COMPOSER_PID_PATH.exists(): - settings.COMPOSER_PID_PATH.parent.mkdir(parents=True, exist_ok=True) - with open(settings.COMPOSER_PID_PATH, "w", encoding="utf8") as f: + file = get_pid_file(procfile) + if not file.exists(): + file.parent.mkdir(parents=True, exist_ok=True) + with open(file, "w", encoding="utf8") as f: _ = f.write(str(pid)) -def delete_pid(): +def delete_pid(procfile: Path): """Delete PID file for cleanup""" - settings.COMPOSER_PID_PATH.unlink(missing_ok=True) + get_pid_file(procfile).unlink(missing_ok=True) -def is_composer_running() -> bool: +def is_composer_running(procfile: Path) -> bool: """Check if the process in the PID file is running""" - pid = get_pid() + pid = get_pid(procfile) if pid is None: return False try: @@ -45,25 +52,29 @@ def start_composer(procfile: Path): """Starts the composer and stores the PID as an environment variable This allows for running smoothly with the django reloader """ - if is_composer_running(): - logging.info(f"Composer is already running with pid {get_pid()}") + if is_composer_running(procfile): logging.info( - f"If this is a mistake, please delete {settings.COMPOSER_PID_PATH} and restart the process" + f"Composer for {procfile} is already running with pid {get_pid(procfile)}" + ) + logging.info( + f"If this is a mistake, please delete {get_pid_file(procfile)} and restart the process" ) return process = subprocess.Popen( [sys.executable, "-m", "honcho", "-f", str(procfile), "start"], ) - write_pid(process.pid) + write_pid(procfile, process.pid) -def stop_composer(): +def stop_composer(procfile: Path): """Stops the composer if it was started before""" - if is_composer_running(): - process = psutil.Process(get_pid()) + if is_composer_running(procfile): + process = psutil.Process(get_pid(procfile)) if process.parent() != psutil.Process(): - logging.info("Currently running composer is controlled by another process") + logging.info( + f"Currently running composer for {procfile} is controlled by another process" + ) return process.send_signal(signal.SIGTERM) _ = process.wait() - delete_pid() + delete_pid(procfile) diff --git a/sith/pytest.py b/sith/pytest.py index 844f49d2..f3cb4ec6 100644 --- a/sith/pytest.py +++ b/sith/pytest.py @@ -3,7 +3,7 @@ import atexit import pytest from .composer import start_composer, stop_composer -from .settings import PROCFILE_MINIMAL +from .settings import PROCFILE_SERVICE # it's the first hook loaded by pytest and can only # be defined in a proper pytest plugin @@ -15,6 +15,6 @@ from .settings import PROCFILE_MINIMAL @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): """Hook that loads the composer before the pytest-django plugin""" - if PROCFILE_MINIMAL is not None: - start_composer(PROCFILE_MINIMAL) - _ = atexit.register(stop_composer) + if PROCFILE_SERVICE is not None: + start_composer(PROCFILE_SERVICE) + _ = atexit.register(stop_composer, procfile=PROCFILE_SERVICE) diff --git a/sith/settings.py b/sith/settings.py index dab15139..f75b1ac9 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -64,11 +64,8 @@ def optional_file_parser(value: str) -> Path | None: BASE_DIR = Path(__file__).parent.parent.resolve() # Composer settings -PROCFILE_FULL = env.optional_file("PROCFILE_FULL", None) -PROCFILE_MINIMAL = env.optional_file("PROCFILE_MINIMAL", None) - -## File path used to avoid running the composer multiple times at the same time -COMPOSER_PID_PATH = env.path("COMPOSER_PID_PATH", BASE_DIR / "composer.pid") +PROCFILE_STATIC = env.optional_file("PROCFILE_STATIC", None) +PROCFILE_SERVICE = env.optional_file("PROCFILE_SERVICE", None) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index 8dd88538..7138dee1 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -1,7 +1,13 @@ +import atexit +import os + +from django.conf import settings from django.contrib.staticfiles.management.commands.runserver import ( Command as Runserver, ) +from django.utils.autoreload import DJANGO_AUTORELOAD_ENV +from sith.composer import start_composer, stop_composer from staticfiles.processors import OpenApi @@ -10,4 +16,10 @@ class Command(Runserver): def run(self, **options): OpenApi.compile() + if ( + os.environ.get(DJANGO_AUTORELOAD_ENV) is None + and settings.PROCFILE_STATIC is not None + ): + start_composer(settings.PROCFILE_STATIC) + _ = atexit.register(stop_composer, procfile=settings.PROCFILE_STATIC) super().run(**options)