jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1191816?usp=email )
Change subject: MyPy: solve MyPy issues in cosmetic_changes.py and titletranslate.py
......................................................................
MyPy: solve MyPy issues in cosmetic_changes.py and titletranslate.py
Change-Id: I6d2643d47fd4760d061eef8e1d13d4ea9c7418a7
---
M .pre-commit-config.yaml
M conftest.py
M pywikibot/cosmetic_changes.py
M pywikibot/titletranslate.py
4 files changed, 11 insertions(+), 13 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 872128b..6e18061 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -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|daemonize|diff|echo|exceptions|fixes|logging|plural|time)|
+ (__metadata__|backports|config|cosmetic_changes|daemonize|diff|echo|exceptions|fixes|logging|plural|time|titletranslate)|
(comms|data|families|specialbots)/__init__|
comms/eventstreams|
data/(api/(__init__|_optionset)|memento|wikistats)|
diff --git a/conftest.py b/conftest.py
index d34593e..a314ee8 100644
--- a/conftest.py
+++ b/conftest.py
@@ -16,8 +16,8 @@
EXCLUDE_PATTERN = re.compile(
r'(?:'
- r'(__metadata__|backports|config|daemonize|diff|echo|exceptions|fixes|'
- r'logging|plural|time)|'
+ r'(__metadata__|backports|config|cosmetic_changes|daemonize|diff|echo|'
+ r'exceptions|fixes|logging|plural|time|titletranslate)|'
r'(comms|data|families|specialbots)/__init__|'
r'comms/eventstreams|'
r'data/(api/(__init__|_optionset)|memento|wikistats)|'
diff --git a/pywikibot/cosmetic_changes.py b/pywikibot/cosmetic_changes.py
index af94592..2d87ed6 100644
--- a/pywikibot/cosmetic_changes.py
+++ b/pywikibot/cosmetic_changes.py
@@ -369,10 +369,8 @@
if not self.talkpage:
subpage = False
if self.template:
- loc = None
- with suppress(TypeError):
- _tmpl, loc = i18n.translate(self.site.code, moved_links)
- if loc is not None and loc in self.title:
+ loc = i18n.translate(self.site.code, moved_links)
+ if loc is not None and loc[1] in self.title:
subpage = True
# get interwiki
diff --git a/pywikibot/titletranslate.py b/pywikibot/titletranslate.py
index 1672004..1d381be 100644
--- a/pywikibot/titletranslate.py
+++ b/pywikibot/titletranslate.py
@@ -1,6 +1,6 @@
"""Title translate module."""
#
-# (C) Pywikibot team, 2003-2024
+# (C) Pywikibot team, 2003-2025
#
# Distributed under the terms of the MIT license.
#
@@ -46,7 +46,7 @@
for h in hints:
# argument may be given as -hint:xy where xy is a language code
- codes, _, newname = h.partition(':')
+ code, _, newname = h.partition(':')
if not newname:
# if given as -hint:xy or -hint:xy:, assume that there should
# be a page in language xy with the same title as the page
@@ -55,12 +55,12 @@
continue
newname = page.title(with_ns=False,
without_brackets=removebrackets)
- if codes.isdigit():
- codes = site.family.languages_by_size[:int(codes)]
- elif codes == 'all':
+ if code.isdigit():
+ codes = site.family.languages_by_size[:int(code)]
+ elif code == 'all':
codes = list(site.family.codes)
else:
- codes = site.family.language_groups.get(codes, codes.split(','))
+ codes = site.family.language_groups.get(code, code.split(','))
for newcode in codes:
if newcode in site.codes:
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1191816?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: I6d2643d47fd4760d061eef8e1d13d4ea9c7418a7
Gerrit-Change-Number: 1191816
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
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 <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot