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

Change subject: [IMPR] Provide an entry point to connect foreign scripts with 
pwb wapper
......................................................................

[IMPR] Provide an entry point to connect foreign scripts with pwb wapper

- modify the wrapper script to support pywikibot entry points to support
  script packages with Pywikibot. The entry point is just the package
  path. It also checks for i18n files and sets the message package.
- define base directory of the scripts which is connected to the entry
  point.
- add pyproject.toml for the script package
- add a MANIFEST.in for the script package
- modify make_dist.py to support creating the pywikibot or the
  pywikibot-scripts distribution.
- add documentation for providing external scripts
- add sphinx-tabs
- ignore directive tabs with rstcheck as it cause warnings

Bug: T139143
Bug: T139144
Change-Id: I5705d240639190e0260299241d2bb76201dde7d2
---
M .rstcheck.cfg
M docs/conf.py
A docs/entrypoint.rst
M docs/index.rst
M docs/requirements.txt
M make_dist.py
M pywikibot/scripts/wrapper.py
A scripts/MANIFEST.in
M scripts/README.rst
M scripts/__init__.py
A scripts/pyproject.toml
M tests/make_dist_tests.py
12 files changed, 341 insertions(+), 27 deletions(-)

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




diff --git a/.rstcheck.cfg b/.rstcheck.cfg
index a3e2b52..918cfcf 100644
--- a/.rstcheck.cfg
+++ b/.rstcheck.cfg
@@ -1,4 +1,4 @@
 [rstcheck]
-ignore_directives=automodule,autoclass,autofunction
+ignore_directives=automodule,autoclass,autofunction,tabs
 ignore_messages=(Undefined substitution referenced: "(release|today|version)")
 ignore_roles=api,phab,pylib,source,wiki
diff --git a/docs/conf.py b/docs/conf.py
index 6d05366..9e40c20 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -58,6 +58,7 @@
 extensions = [
     'notfound.extension',
     'sphinx_copybutton',
+    'sphinx_tabs.tabs',
     'sphinx.ext.autodoc',
     'sphinx.ext.autosectionlabel',
     'sphinx.ext.autosummary',
diff --git a/docs/entrypoint.rst b/docs/entrypoint.rst
new file mode 100644
index 0000000..30c75e2
--- /dev/null
+++ b/docs/entrypoint.rst
@@ -0,0 +1,97 @@
+************************
+Provide your own scripts
+************************
+
+You may provide your own scripts as a **Pywikibot** plugin. All you have
+to do is to bind your package and define an entry point for pywikibot.
+
+.. caution:: ``pywikibot >= 9.4.0`` is required for this possibility.
+
+For example having package files like this::
+
+  my_pwb_scripts/
+  ├── LICENSE
+  ├── pyproject.toml
+  ├── README.md
+  ├── src/
+  │   └── wikidata_scripts/
+  │       ├── __init__.py
+  │       ├── drop_entities.py
+  │       └── show_entities.py
+  └── tests/
+
+
+Add the following code in your ``wikidata_scripts.__init__.py``:
+
+.. code-block:: python
+
+   from pathlib import Path
+   base_dir = Path(__file__).parent
+
+Add *Pywikibot* dependency and register the entry point, which is the
+``base_dir`` above, within your preferred config file:
+
+.. tabs::
+   .. tab:: pyproject.toml
+
+      .. code-block:: toml
+
+         [project]
+         dependencies = [
+             "pywikibot >= 9.4.0",
+         ]
+
+         [project.entry-points."pywikibot"]
+         scriptspath = "wikidata_scripts:base_dir"
+
+
+   .. tab:: setup.cfg
+
+      .. code-block:: ini
+
+         [options]
+         install_requires =
+             pywikibot >= 9.4.0
+
+         [options.entry_points]
+         pywikibot =
+             scriptspath = wikidata_scripts:base_dir
+
+   .. tab:: setup.py
+
+      .. code-block:: python
+
+         from setuptools import setup
+
+         setup(
+             install_requires=[
+                 'pywikibot >= 9.4.0',
+             ],
+             entry_points={
+                 'pywikibot': [
+                     'scriptspath = wikidata_scripts:base_dir',
+                 ]
+             }
+         )
+
+After installing your package scripts are available via :mod:`pwb` wrapper and
+can be invoked like this:
+
+.. tabs::
+
+   .. tab:: Unix/macOS
+
+      .. code-block:: shell
+
+         $ pwb <global options> show_entities <scripts options>
+
+   .. tab:: Windows
+
+      .. code-block:: shell
+
+         pwb <global options> show_entities <scripts options>
+
+.. note:: If you have several Pywikibot scripts installed, there script names
+   must be different; otherwise the started script might not that you have
+   expected.
+.. warning:: This guide is not tested. Test it locally before uploading to 
pypi.
diff --git a/docs/index.rst b/docs/index.rst
index 0586a69..7929f58 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -65,6 +65,7 @@
    recipes
    api_ref/index
    mwapi
+   entrypoint

 .. toctree::
    :maxdepth: 1
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 646a0d0..faa90f8 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -6,5 +6,6 @@
 sphinxext-opengraph >= 0.9.1
 sphinx-copybutton >= 0.5.2
 sphinx-notfound-page >= 1.0.4
+sphinx-tabs >= 3.4.5
 tomli; python_version < '3.11'
 furo >= 2024.8.6
diff --git a/make_dist.py b/make_dist.py
index 2c0ca36..7ac65dc 100755
--- a/make_dist.py
+++ b/make_dist.py
@@ -3,6 +3,10 @@

 The following options are supported:

+pywikibot  The pywikibot repository to build (default)
+
+scripts    The pywikibot-scripts repository to build
+
 -help      Print documentation of this file and of setup.py

 -local     Install the distribution as a local site-package. If a
@@ -21,7 +25,7 @@

 Usage::

-    [pwb] make_dist [options]
+    [pwb] make_dist [repo] [options]

 .. versionadded:: 7.3
 .. versionchanged:: 7.4
@@ -43,6 +47,9 @@
 .. versionchanged:: 8.2
    Build frontend was changed from setuptools to build. ``-upgrade``
    option also installs packages if necessary.
+
+.. versionchanged:: 9.4
+   The pywikibot-scripts distribution can be created.
 """
 #
 # (C) Pywikibot team, 2022-2024
@@ -54,6 +61,7 @@
 import abc
 import shutil
 import sys
+from contextlib import suppress
 from dataclasses import dataclass, field
 from importlib import import_module
 from pathlib import Path
@@ -76,6 +84,8 @@
     remote: bool
     clear: bool
     upgrade: bool
+
+    build_opt: str = field(init=False)
     folder: Path = field(init=False)

     def __post_init__(self) -> None:
@@ -91,6 +101,8 @@
         shutil.rmtree(self.folder / 'build', ignore_errors=True)
         shutil.rmtree(self.folder / 'dist', ignore_errors=True)
         shutil.rmtree(self.folder / 'pywikibot.egg-info', ignore_errors=True)
+        shutil.rmtree(self.folder / 'scripts' / 'pywikibot_scripts.egg-info',
+                      ignore_errors=True)
         info('<<lightyellow>>done')

     @abc.abstractmethod
@@ -139,7 +151,7 @@
         self.copy_files()
         info('<<lightyellow>>Build package')
         try:
-            check_call('python -m build')
+            check_call(f'python -m build {self.build_opt}')
         except Exception as e:
             error(e)
             return False
@@ -152,10 +164,9 @@

         if self.local:
             info('<<lightyellow>>Install locally')
-            check_call('pip uninstall pywikibot -y', shell=True)
-            check_call(
-                'pip install --no-index --pre --find-links=dist pywikibot',
-                shell=True)
+            check_call(f'pip uninstall {self.package} -y', shell=True)
+            check_call(f'pip install --no-cache-dir --no-index --pre '
+                       f'--find-links=dist {self.package}', shell=True)

         if self.remote and input_yn(
                 '<<lightblue>>Upload dist to pypi', automatic_quit=False):
@@ -170,6 +181,9 @@
     .. versionadded:: 8.0
     """

+    build_opt = ''  # defaults to current directory
+    package = 'pywikibot'
+
     def __init__(self, *args) -> None:
         """Set source and target directories."""
         super().__init__(*args)
@@ -203,6 +217,36 @@
         info('<<lightyellow>>done')


+class SetupScripts(SetupBase):
+
+    """Setup pywikibot-scripts distribution.
+
+    .. versionadded:: 9.4
+    """
+
+    build_opt = '-w'  # only wheel (yet)
+    package = 'pywikibot_scripts'
+    replace = 'MANIFEST.in', 'pyproject.toml', 'setup.py'
+
+    def copy_files(self) -> None:
+        """Ignore copy files yet."""
+        info('<<lightyellow>>Copy files ...', newline=False)
+        for filename in self.replace:
+            file = self.folder / filename
+            file.rename(self.folder / (filename + '.saved'))
+            with suppress(FileNotFoundError):
+                shutil.copy(self.folder / 'scripts' / filename, self.folder)
+        info('<<lightyellow>>done')
+
+    def cleanup(self) -> None:
+        """Ignore cleanup yet."""
+        info('<<lightyellow>>Copy files ...', newline=False)
+        for filename in self.replace:
+            file = self.folder / (filename + '.saved')
+            file.replace(self.folder / filename)
+        info('<<lightyellow>>done')
+
+
 def handle_args() -> tuple[bool, bool, bool, bool]:
     """Handle arguments and print documentation if requested.

@@ -223,19 +267,21 @@
     remote = '-remote' in sys.argv
     clear = '-clear' in sys.argv
     upgrade = '-upgrade' in sys.argv
+    scripts = 'scripts' in sys.argv

     if remote and 'dev' in __version__:  # pragma: no cover
         warning('Distribution must not be a developmental release to upload.')
         remote = False

     sys.argv = [sys.argv[0]]
-    return local, remote, clear, upgrade
+    return local, remote, clear, upgrade, scripts


 def main() -> None:
     """Script entry point."""
-    args = handle_args()
-    return SetupPywikibot(*args).run()
+    *args, scripts = handle_args()
+    installer = SetupScripts if scripts else SetupPywikibot
+    return installer(*args).run()


 if __name__ == '__main__':
diff --git a/pywikibot/scripts/wrapper.py b/pywikibot/scripts/wrapper.py
index bba3591..fb64afd0 100755
--- a/pywikibot/scripts/wrapper.py
+++ b/pywikibot/scripts/wrapper.py
@@ -9,7 +9,8 @@
 2. User scripts residing in `scripts/userscripts` (directory mode only).
 3. Scripts residing in `scripts` folder (directory mode only).
 4. Maintenance scripts residing in `scripts/maintenance` (directory mode only).
-5. Framework scripts residing in `pywikibot/scripts`.
+5. Site-package scripts (site-package only)
+6. Framework scripts residing in `pywikibot/scripts`.

 This wrapper script is able to invoke scripts even if the script name is
 misspelled. In directory mode it also checks package dependencies.
@@ -38,6 +39,8 @@
    see :ref:`Environment variables`.
 .. versionchanged:: 8.0
    renamed to wrapper.py.
+.. versionchanged:: 9.4
+   enable external scripts via entry points.
 """
 #
 # (C) Pywikibot team, 2012-2024
@@ -382,6 +385,8 @@
        Search users_scripts_paths in config.base_dir
     .. versionchanged:: 9.0
        Add config.base_dir to search path
+    .. versionchanged:: 9.4
+       Search in entry point paths
     """
     from pywikibot import config
     path_list = []  # paths to find misspellings
@@ -397,6 +402,7 @@
             path_list.append(testpath.parent)
         return None

+    # search through user scripts paths
     user_script_paths = ['']
     if config.user_script_paths:  # pragma: no cover
         if isinstance(config.user_script_paths, list):
@@ -410,7 +416,22 @@
     if found:  # pragma: no cover
         return found

-    if not site_package:
+    if site_package:  # search for entry points
+        import importlib
+        from importlib.metadata import entry_points
+
+        from pywikibot.i18n import set_messages_package
+
+        for ep in entry_points(name='scriptspath', group='pywikibot'):
+            path = ep.load()
+            found = test_paths([''], path)
+            if found:
+                i18n_package = path.stem + '.i18n'
+                if importlib.import_module(i18n_package).__file__ is not None:
+                    set_messages_package(i18n_package)
+                return found
+
+    else:  # search in scripts folder
         script_paths = [
             'scripts.userscripts',
             'scripts',
diff --git a/scripts/MANIFEST.in b/scripts/MANIFEST.in
new file mode 100644
index 0000000..fae3dfb
--- /dev/null
+++ b/scripts/MANIFEST.in
@@ -0,0 +1,2 @@
+graft scripts/i18n/
+include CODE_OF_CONDUCT.rst
diff --git a/scripts/README.rst b/scripts/README.rst
index 881889d..2f8687f 100644
--- a/scripts/README.rst
+++ b/scripts/README.rst
@@ -1,7 +1,16 @@
+.. image:: https://img.shields.io/github/languages/top/wikimedia/pywikibot
+   :alt: Top language
+   :target: https://www.python.org/downloads/
+.. image:: https://img.shields.io/github/last-commit/wikimedia/pywikibot
+   :alt: Last commit
+   :target: https://gerrit.wikimedia.org/r/plugins/gitiles/pywikibot/core/
+
 ###########################################################################
 **This is a package to include robots for MediaWiki wikis like Wikipedia.**
 ###########################################################################

+.. role:: api
+
 *********************************
 Some example robots are included.
 *********************************
@@ -179,18 +188,6 @@
 | unidata.py             | Updates _first_upper_exception_dict in 
tools.unidata    |
 
+------------------------+---------------------------------------------------------+

-Others
-======
-
-+------------------------+---------------------------------------------------------+
-| Others                 |                                                     
    |
-+========================+=========================================================+
-| i18n (folder)          | Contains i18n translations for bot edit summaries.  
    |
-+------------------------+---------------------------------------------------------+
-| userscripts (folder)   | Empty folder for user scripts.                      
    |
-+------------------------+---------------------------------------------------------+
-| README.rst             | This file (Short info of all scripts).              
    |
-+------------------------+---------------------------------------------------------+

 **External packages could be required with Pywikibot:**

diff --git a/scripts/__init__.py b/scripts/__init__.py
index 67c172f..7cc825a 100644
--- a/scripts/__init__.py
+++ b/scripts/__init__.py
@@ -31,5 +31,10 @@
 #
 from __future__ import annotations

+from pathlib import Path

-__version__ = '9.4.0'
+
+__version__ = '9.4.0.dev1'
+
+#: defines the entry point for pywikibot-scripts package
+base_dir = Path(__file__).parent
diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml
new file mode 100644
index 0000000..b2f915f
--- /dev/null
+++ b/scripts/pyproject.toml
@@ -0,0 +1,132 @@
+[build-system]
+requires = ["packaging", "setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools]
+package-dir = {"pywikibot_scripts" = "scripts"}
+
+[project]
+name = "pywikibot-scripts"
+version = "9.4.0"
+
+authors = [
+    {name = "xqt", email = "i...@gno.de"},
+]
+maintainers = [
+    {name = "The Pywikibot team", email = "pywiki...@lists.wikimedia.org"},
+]
+description = "Pywikibot Scripts Collection"
+readme = "scripts/README.rst"
+requires-python = ">=3.7.0"
+dependencies = [
+    "pywikibot >= 9.4.0.dev0",
+    "isbnlib",
+    "langdetect",
+    "mwparserfromhell",
+    "pydot",
+    "requests",
+    "unidecode",
+]
+
+keywords = [
+    "add text", "archivebot", "basic", "bot", "client", "framework", 
"mediawiki",
+    "pwb", "pybot", "python",
+    "pywiki", "pywikibase", "pywikibot", "pywikipedia", "pywikipediabot",
+    "replace", "redirect", "script", "scripts", "upload",
+    "wiki", "wikibase", "wikidata", "wikimedia", "wikipedia",
+]
+license = {text = "MIT License"}
+classifiers=[
+    "Development Status :: 5 - Production/Stable",
+    "Environment :: Console",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Natural Language :: Afrikaans",
+    "Natural Language :: Arabic",
+    "Natural Language :: Basque",
+    "Natural Language :: Bengali",
+    "Natural Language :: Bosnian",
+    "Natural Language :: Bulgarian",
+    "Natural Language :: Cantonese",
+    "Natural Language :: Catalan",
+    "Natural Language :: Chinese (Simplified)",
+    "Natural Language :: Chinese (Traditional)",
+    "Natural Language :: Croatian",
+    "Natural Language :: Czech",
+    "Natural Language :: Danish",
+    "Natural Language :: Dutch",
+    "Natural Language :: English",
+    "Natural Language :: Esperanto",
+    "Natural Language :: Finnish",
+    "Natural Language :: French",
+    "Natural Language :: Galician",
+    "Natural Language :: German",
+    "Natural Language :: Greek",
+    "Natural Language :: Hebrew",
+    "Natural Language :: Hindi",
+    "Natural Language :: Hungarian",
+    "Natural Language :: Icelandic",
+    "Natural Language :: Indonesian",
+    "Natural Language :: Irish",
+    "Natural Language :: Italian",
+    "Natural Language :: Japanese",
+    "Natural Language :: Javanese",
+    "Natural Language :: Korean",
+    "Natural Language :: Latin",
+    "Natural Language :: Latvian",
+    "Natural Language :: Lithuanian",
+    "Natural Language :: Macedonian",
+    "Natural Language :: Malay",
+    "Natural Language :: Marathi",
+    "Natural Language :: Nepali",
+    "Natural Language :: Norwegian",
+    "Natural Language :: Panjabi",
+    "Natural Language :: Persian",
+    "Natural Language :: Polish",
+    "Natural Language :: Portuguese",
+    "Natural Language :: Portuguese (Brazilian)",
+    "Natural Language :: Romanian",
+    "Natural Language :: Russian",
+    "Natural Language :: Serbian",
+    "Natural Language :: Slovak",
+    "Natural Language :: Slovenian",
+    "Natural Language :: Spanish",
+    "Natural Language :: Swedish",
+    "Natural Language :: Tamil",
+    "Natural Language :: Telugu",
+    "Natural Language :: Thai",
+    "Natural Language :: Tibetan",
+    "Natural Language :: Turkish",
+    "Natural Language :: Ukrainian",
+    "Natural Language :: Urdu",
+    "Natural Language :: Vietnamese",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
+    "Programming Language :: Python :: 3.14",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
+    "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Wiki",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: Utilities",
+]
+
+[project.entry-points."pywikibot"]
+scriptspath = "pywikibot_scripts:base_dir"
+
+[project.urls]
+Homepage = "https://www.mediawiki.org/wiki/Manual:Pywikibot";
+Documentation = "https://doc.wikimedia.org/pywikibot/stable/";
+Repository = "https://gerrit.wikimedia.org/r/plugins/gitiles/pywikibot/core/";
+"GitHub Mirror" = "https://github.com/wikimedia/pywikibot";
+Download = "https://www.pywikibot.org";
+Changelog = "https://doc.wikimedia.org/pywikibot/master/changelog.html";
+Tracker = "https://phabricator.wikimedia.org/tag/pywikibot/";
diff --git a/tests/make_dist_tests.py b/tests/make_dist_tests.py
index 8b99c1e..671f4c3 100755
--- a/tests/make_dist_tests.py
+++ b/tests/make_dist_tests.py
@@ -24,16 +24,27 @@
     def test_handle_args_empty(self):
         """Test make_dist handle_args function."""
         args = make_dist.handle_args()
-        self.assertEqual(args, (False, ) * 4)
+        self.assertEqual(args, (False, ) * 5)
+
+    def test_handle_args_scripts(self):
+        """Test make_dist handle_args function."""
+        sys.argv += ['-local', 'scripts', '-remote']
+        local, remote, clear, upgrade, scripts = make_dist.handle_args()
+        self.assertTrue(local)
+        self.assertEqual(remote, 'dev' not in __version__)
+        self.assertFalse(clear)
+        self.assertFalse(upgrade)
+        self.assertTrue(scripts)

     def test_handle_args(self):
         """Test make_dist handle_args function."""
         sys.argv += ['-clear', '-local', '-remote', '-upgrade']
-        local, remote, clear, upgrade = make_dist.handle_args()
+        local, remote, clear, upgrade, scripts = make_dist.handle_args()
         self.assertTrue(local)
         self.assertEqual(remote, 'dev' not in __version__)
         self.assertTrue(clear)
         self.assertTrue(upgrade)
+        self.assertFalse(scripts)

     def test_main(self):
         """Test main result."""

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/842959?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: I5705d240639190e0260299241d2bb76201dde7d2
Gerrit-Change-Number: 842959
Gerrit-PatchSet: 29
Gerrit-Owner: Xqt <i...@gno.de>
Gerrit-Reviewer: D3r1ck01 <dalangi-...@wikimedia.org>
Gerrit-Reviewer: Xqt <i...@gno.de>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- pywikibot-commits@lists.wikimedia.org
To unsubscribe send an email to pywikibot-commits-le...@lists.wikimedia.org

Reply via email to