Page MenuHomePhabricator

pwb console script does not work when pip installed from git
Open, MediumPublicBUG REPORT

Description

Steps to replicate the issue:

  • Checkout any version of pywikibot since 7.0.0 from git
  • Install it using pip (e.g., pip install .[mwparserfromhell])
  • Attempt to use the installed pwb console script to run one of the install scripts (e.g., pwb version)

What happens?:

$ pwb version
Traceback (most recent call last):
  File "/home/jjmc89/wrk/pwb/venv/bin/pwb", line 5, in <module>
    from pywikibot.scripts.pwb import run
ModuleNotFoundError: No module named 'pywikibot.scripts.pwb'
CRITICAL: Exiting due to uncaught exception <class 'ModuleNotFoundError'>

What should have happened instead?:

It should install via pip with a usable pwb console script.

Software version:
7.0.0 - current (feed2df)

Other information:
i18n is also not installed the same as it would be from PyPI.

Event Timeline

pwb code entry point was introduced in release 7.0.0 with rPWBC68f381a. No scripts were supported with previous releases as a site-package installation, neither pwb.py, the generate_*.py scripts nore even version.py. The current packaging collects all files inside the pywikibot folder but the pwb.py wrapper is outside of this scope. Therefore the make_dist.py script was added which copies the wrapper script into the pywikibot directory, creates the distribution and cleanups the copied file(s).

There are two ways to solve this task:

  1. Add a documentation hint to the documentation not to install a pywikibot site-package diectly from a repository but use make_dist script instead. That would be ok to install from a local checkout but fails when installing from a remote git repository.
  2. move the pwb.py script to the pywikibot/scripts folder (as well as a minimal i18n pywikibot translation file) and add a caller script to the main root like (code snippet from pwb.exe entry point):
import sys
from pywikibot.scripts.pwb import main
if __name__ == '__main__':
    sys.exit(main())

Change 842909 had a related patch set uploaded (by Xqt; author: Xqt):

[pywikibot/core@master] [FXI] Enable site-package installation from git repository (Part 1)

https://gerrit.wikimedia.org/r/842909

Change 842929 had a related patch set uploaded (by Xqt; author: Xqt):

[pywikibot/core@master] [FIX] Enable site-package installation from git repository (Part 2)

https://gerrit.wikimedia.org/r/842929

Xqt triaged this task as Medium priority.Oct 15 2022, 3:09 PM

We shouldn't be using a custom way to make the distribution. That is just asking for things to be broken like this.

It would be simplest from a packaging standpoint to change the git directory structure (below) and distribute three packages in the current pywikibot.

current
├── pywikibot
├── scripts
│   └── i18n
└── pwb.py
proposed
├── pywikibot
├── pywikibot_i18n
└── pywikibot_scripts
    └── pwb.py

This is more difficult: On one side we have the directory mode with the pywkibot framework and the script in it and a lot of bot owners use it in that way. But also the CI tests works with that structure. On the other hand we have the Python Package Index and the Pywikibot framework is shipped in this way. Currently we have this structure:

pywikibot-core
├── make_dist.py
├── pwb.py
├── setup.py
├── pywikibot
│   └── scripts
│      └── <scripts>
│      └── i18n
│          └── <pywikibot-bundle>
├── scripts
│  └── <scripts>
│  └── i18n
│      └── <bundles>
├── docs
├── tests

pywikibot-i18n
├── <bundles>

pywikibot-delinker
...

scripts/i18n is embedded as subpackage and works more or less. Currently make_dist.py copies the pywikibot bundle to the pywikibot/scripts/i18n and the pwb.py wrapper script to pywikibot/scripts. This works because there is a scripts entry point which calls pywikibot.scripts.pwb:run().

With thos change the pwb.py wrapper scripts is renamed to wrapper.py and resides in pywikibot/scripts. The pwb.py becomes a caller script which is necessary for directory mode only. make_dist.py only copies the pywikibot i18n bundle but this can be omitted because a minimal 'en' bundle is already there. This minimal change enables installing pywikibot as a site-package from a git (or svn?) repository instead from the PyPI.

I think it is easy to change to that structure that you proposed. Instead having the submodule inside scripts path we have it in root directory of the pywikibot-core framework.
This is easy to implement for directory mode but I've no glue yet how to include the package date if it is outside the package (pywikibot) folder.

Another problem is to bundle pywikibot-scripts as a site-package (like here). setup.py cannot be used for both packages and you cannot have different script names for setup.

You only need one setup.py that will install multiple site-packages. I did some quick testing by updating as below and things seem to work. It installs 3 site-packages: pywikibot, pywikibot_i18n, and pywikibot_scripts.

pywikibot-core
├── MANIFEST.in        # updated as below
├── pywikibot
├── pywikibot_i18n     # moved from ./scripts/i18n (git submodule pywikibot-i18n)
├── pywikibot_scripts  # from current ./scripts but without i18n and ./pywikibot/scripts
│   └── __main__.py    # moved from ./pwb.py
├── docs               # [git only] unchanged
├── maintenance        # [git only] if needed for maintenance scripts that should not be packaged/distributed
├── tests              # [git only] unchanged
├── pwb.py             # [git only] if needed temporarily for compatibility
└── setup.py           # updated as below
diff --git a/MANIFEST.in b/MANIFEST.in
index 0952697fc..32cf5355a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1,2 @@
-graft pywikibot/scripts/i18n/pywikibot
\ No newline at end of file
+include pywikibot_i18n/*/*.json
+
diff --git a/setup.py b/setup.py
index e0e424ea5..5f15c1951 100755
--- a/setup.py
+++ b/setup.py
@@ -234,7 +234,7 @@ def get_packages(name) -> List[str]:  # pragma: no cover
     except ImportError:
         sys.exit(
             'setuptools >= 40.1.0 is required to create a new distribution.')
-    packages = find_namespace_packages(include=[name + '.*'])
+    packages = find_namespace_packages(include=[name + '*'])
     return [str(name)] + packages


@@ -289,7 +289,7 @@ def main() -> None:  # pragma: no cover
         },
         entry_points={
             'console_scripts': [
-                'pwb = pywikibot.scripts.pwb:run',
+                'pwb = pywikibot_scripts.__main__:run',
             ],
         },
         classifiers=[

You only need one setup.py that will install multiple site-packages.

Sure but is that really what is wanted? People should have the choice importing the framework and pywikibot_scripts should be installed optional (and pywikibot-scripts become its own package at Python Package Index). On the other hand installing from the repository instead from PyPI and having both installed would also be fine if PyPI is an alternative. So we can have both ways.

Change 842909 merged by Xqt:

[pywikibot/core@master] [FIX] Enable site-package installation from git repository (Part 1)

https://gerrit.wikimedia.org/r/842909

Change 842929 merged by jenkins-bot:

[pywikibot/core@master] [FIX] Enable site-package installation from git repository (Part 2)

https://gerrit.wikimedia.org/r/842929

Sure but is that really what is wanted?

Yes.

People should have the choice importing the framework and pywikibot_scripts should be installed optional (and pywikibot-scripts become its own package at Python Package Index).

Why? I don't see any real benefit to having a separate PyPI package, only a substantial maintenance burden. After all, some scripts are already in the current PyPI package.