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