jenkins-bot has submitted this change. ( 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1191813?usp=email )

Change subject: IMPT: daemonize improvements
......................................................................

IMPT: daemonize improvements

- enforce keyword-only parameters and add deprecation warning
- raise a NotImplementedError if called under non-posix os
- suppress OSError when closing standard streams
- Update and expand docstring
- add daemonize to pre-commit MyPy tests

Change-Id: I50ae24b2567c092add345a842ae0c2035a2be0e4
---
M .pre-commit-config.yaml
M ROADMAP.rst
M conftest.py
M pywikibot/daemonize.py
4 files changed, 89 insertions(+), 8 deletions(-)

Approvals:
  Xqt: Looks good to me, approved
  jenkins-bot: Verified




diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 10a958f..872128b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -65,7 +65,7 @@
         language: python
         require_serial: true
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.13.1
+    rev: v0.13.2
     hooks:
       - id: ruff-check
         alias: ruff
@@ -126,7 +126,7 @@
         # They should be also used in conftest.py to exclude them from 
non-voting mypy test.
         files: |
           (?x)^pywikibot/(
-            
(__metadata__|backports|config|diff|echo|exceptions|fixes|logging|plural|time)|
+            
(__metadata__|backports|config|daemonize|diff|echo|exceptions|fixes|logging|plural|time)|
             (comms|data|families|specialbots)/__init__|
             comms/eventstreams|
             data/(api/(__init__|_optionset)|memento|wikistats)|
diff --git a/ROADMAP.rst b/ROADMAP.rst
index 69367fd..3e5439f 100644
--- a/ROADMAP.rst
+++ b/ROADMAP.rst
@@ -1,6 +1,8 @@
 Current Release Changes
 =======================

+* Positional arguments of :func:`daemonize()<daemonize.daemonize>` are 
deprecated and must
+  be given as keyword arguments.
 * Return :meth:`bot.BaseBot.userPut` result with 
:meth:`AutomaticTWSummaryBot.put_current()
   <bot.AutomaticTWSummaryBot.put_current>` method

@@ -11,6 +13,8 @@
 Pending removal in Pywikibot 13
 -------------------------------

+* 10.6.0: Positional arguments of :func:`daemonize()<daemonize.daemonize>` are 
deprecated and must
+  be given as keyword arguments.
 * 10.5.0: Accessing the fallback '*' keys in 'languages', 'namespaces', 
'namespacealiases', and
   'skins' properties of 
:attr:`APISite.siteinfo<pywikibot.site._apisite.APISite.siteinfo>` are
   deprecated and will be removed.
diff --git a/conftest.py b/conftest.py
index e33738a..d34593e 100644
--- a/conftest.py
+++ b/conftest.py
@@ -16,8 +16,8 @@

 EXCLUDE_PATTERN = re.compile(
     r'(?:'
-    r'(__metadata__|backports|config|diff|echo|exceptions|fixes|logging|'
-    r'plural|time)|'
+    r'(__metadata__|backports|config|daemonize|diff|echo|exceptions|fixes|'
+    r'logging|plural|time)|'
     r'(comms|data|families|specialbots)/__init__|'
     r'comms/eventstreams|'
     r'data/(api/(__init__|_optionset)|memento|wikistats)|'
diff --git a/pywikibot/daemonize.py b/pywikibot/daemonize.py
index ea90c16..9bdc9e5 100644
--- a/pywikibot/daemonize.py
+++ b/pywikibot/daemonize.py
@@ -1,4 +1,54 @@
-"""Module to daemonize the current process on Unix."""
+"""Module to daemonize the current process on POSIX systems.
+
+This module provides a function :func:`daemonize` to turn the current
+Python process into a background daemon process on POSIX-compatible
+operating systems (Linux, macOS, FreeBSD) but not on not WASI Android or
+iOS. It uses the standard double-fork technique to detach the process
+from the controlling terminal and optionally closes or redirects
+standard streams.
+
+Double-fork diagram::
+
+    Original process (parent)
+    ├── fork()  → creates first child
+    │   └─ Parent exits via os._exit() → returns control to terminal
+    │
+    └── First child
+        ├── os.setsid()  → becomes session leader (detaches from terminal)
+        ├── fork()  → creates second child (grandchild)
+        │   └─ First child exits → ensures grandchild is NOT a session leader
+        │
+        └── Second child (Daemon)
+            ├── is_daemon = True
+            ├── Optionally close/redirect standard streams
+            ├── Optionally change working directory
+            └── # Daemon continues here
+                while True:
+                    do_background_work()
+
+The "while True" loop represents the main work of the daemon:
+
+- It runs indefinitely in the background
+- Performs tasks such as monitoring files, processing data, or logging
+- Everything after :func:`daemonize` runs only in the daemon process
+
+Example usage:
+
+    .. code-block:: Python
+
+       import time
+       from pywikibot.daemonize import daemonize
+
+       def background_task():
+           while True:
+               print("Daemon is working...")
+               time.sleep(5)
+
+       daemonize()
+
+       # This code only runs in the daemon process
+       background_task()
+"""
 #
 # (C) Pywikibot team, 2007-2025
 #
@@ -7,11 +57,15 @@
 from __future__ import annotations

 import os
+import platform
 import stat
 import sys
+from contextlib import suppress
 from enum import IntEnum
 from pathlib import Path

+from pywikibot.tools import deprecate_positionals
+

 class StandardFD(IntEnum):

@@ -25,7 +79,9 @@
 is_daemon = False


-def daemonize(close_fd: bool = True,
+@deprecate_positionals(since='10.6.0')
+def daemonize(*,
+              close_fd: bool = True,
               chdir: bool = True,
               redirect_std: str | None = None) -> None:
     """Daemonize the current process.
@@ -33,11 +89,27 @@
     Only works on POSIX compatible operating systems. The process will
     fork to the background and return control to terminal.

+    .. versionchanged:: 10.6
+       raises NotImplementedError instead of AttributeError if daemonize
+       is not available for the given platform. Parameters must be given
+       as keyword-only arguments.
+
+    .. caution::
+       Do not use it in multithreaded scripts or in a subinterpreter.
+
     :param close_fd: Close the standard streams and replace them by
         /dev/null
     :param chdir: Change the current working directory to /
     :param redirect_std: Filename to redirect stdout and stdin to
+    :raises RuntimeError: Must not be run in a subinterpreter
+    :raises NotImplementedError: Daemon mode not supported on given
+        platform
     """
+    # platform check for MyPy
+    if not hasattr(os, 'fork') or sys.platform == 'win32':
+        msg = f'Daemon mode not supported on {platform.system()}'
+        raise NotImplementedError(msg)
+
     # Fork away
     if not os.fork():
         # Become session leader
@@ -50,9 +122,12 @@
             global is_daemon
             is_daemon = True
 
+            # Optionally close and redirect standard streams
             if close_fd:
                 for fd in StandardFD:
-                    os.close(fd)
+                    with suppress(OSError):
+                        os.close(fd)
+
                 os.open('/dev/null', os.O_RDWR)

                 if redirect_std:
@@ -67,9 +142,11 @@
                     os.dup2(StandardFD.STDIN, StandardFD.STDOUT)
                 os.dup2(StandardFD.STDOUT, StandardFD.STDERR)

+            # Optionally change working directory
             if chdir:
                 os.chdir('/')
-            return
+
+            return  # Daemon continues here

         # Write out the pid
         path = Path(Path(sys.argv[0]).name).with_suffix('.pid')

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1191813?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.wikimedia.org/r/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I50ae24b2567c092add345a842ae0c2035a2be0e4
Gerrit-Change-Number: 1191813
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to