After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 680913 - Support overrides in different sys.path directories than pygobject itself
Support overrides in different sys.path directories than pygobject itself
Status: RESOLVED FIXED
Product: pygobject
Classification: Bindings
Component: general
unspecified
Other Linux
: Normal normal
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
: 660647 681217 (view as bug list)
Depends on:
Blocks: 632113
 
 
Reported: 2012-07-31 15:08 UTC by Thibault Saunier
Modified: 2013-05-25 23:55 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Add a way to specify folder where to look for overrides outside of PyGobject (813 bytes, patch)
2012-08-01 16:51 UTC, Thibault Saunier
none Details | Review
Make it possible to add overrides from outside main gi.overrides (1.23 KB, patch)
2012-08-08 20:04 UTC, Thibault Saunier
none Details | Review
Make it possible to add overrides from outside main gi.overrides (1.18 KB, patch)
2012-08-09 16:39 UTC, Thibault Saunier
none Details | Review
test case (failing) (2.56 KB, patch)
2012-08-20 13:51 UTC, Martin Pitt
none Details | Review
(broken) patch and test (4.40 KB, patch)
2012-08-21 06:01 UTC, Martin Pitt
needs-work Details | Review
sys.path cleanup (6.74 KB, patch)
2012-08-23 01:17 UTC, Simon Feltman
none Details | Review

Description Thibault Saunier 2012-07-31 15:08:26 UTC
I started a branch to 'add support' to GStreamer, that is:
  * Add some Regex into pygi.sh
  * Add a Gst.py override file

It is probably still missing a lot, but I believe it is good time to start working on that so GStreamer 1.0 can be usable from pyhton.

Here is the branch: https://github.com/thiblahute/pygobject

(I didn't attach the patches as I am currently working on it but can add them here whenever you want)
Comment 1 Paolo Borelli 2012-07-31 15:28:33 UTC
Great to see things moving!

We spoke briefly about this with Simon (in CC) at Guadec. Our feeling is that these overrides belong in gstreamer itself or in ad-hoc package, because shoving everything in pygobject is not a sustainable solution.

As a general rule I also suggest to keep override at a minimum.

The changes to the convert script are surely welcome.
Comment 2 Thibault Saunier 2012-07-31 15:47:47 UTC
I tottaly agree that none of the overrides should be in pygobject itself, but it is how it is, and I didn't find how that should be done otherwise. It seems that PyGobject is only watching 'gi/overrides' for overrides, so how are we supposed to do it right now?

Thanks for your fast response :)
Comment 3 Martin Pitt 2012-07-31 16:11:39 UTC
There are other libraries which ship their own overrides, such as libdee. They call something like

$ python3 -c 'import gi; print(gi._overridesdir)'
/usr/lib/python3/dist-packages/gi/overrides

$ python -c 'import gi; print(gi._overridesdir)'
/usr/lib/python2.7/dist-packages/gi/overrides

in their build system to find the directory to place overrides in. This is also pointed out in the pkg-config file, but unfortunately pkg-config does not provide enough dynamism to query the path from it.
Comment 4 Thibault Saunier 2012-07-31 16:49:30 UTC
The problem I see is that we can't use it 'uninstalled', would it possible to have PyGObject configure the overrides paths from a list (probably through environment variables)?
Comment 5 Martin Pitt 2012-07-31 21:50:40 UTC
It's not a PyGObject issue, it's a Python one. Python requires all modules of a particular namespace to be in the same directory, so you cannot e. g. have "gi" in /usr/share/python/..., and "gi.overrides.GStreamer" in a different one, as far as I know. This is ugly for local testing for sure. However, perhaps there is a trick with .pth files to do that, we used something similar in Debian when we had pysupport vs. pycentral.
Comment 6 Martin Pitt 2012-07-31 22:12:29 UTC
I did not find an immediate solution, but I guess with some tricks in gi/module.py or gi/__init__.py it ought to be possible to search for overrides in a different directory. So that should either become a new bug, or we devote that aspect to this bug report, as Simon, Pablo, and I all agree that it would be better to keep overrides in their respective projects, like libdee does.

So if the directory issue is the blocker for this, then let's try to fix that instead?
Comment 7 Simon Feltman 2012-07-31 23:36:09 UTC
You can use pkgutil to split a package over multiple paths, see:
http://docs.python.org/library/pkgutil.html
Comment 8 Simon Feltman 2012-07-31 23:44:18 UTC
I've also run into a gi restriction which I don't think is needed. You cannot create overrides outside of a package named "gi.overrides". This restriction doesn't seem necessary. For instance, if I want to create custom overrides in a sub-package of an app, it must live in "gi.overrides". See: pygobject/gi/overrides/__init__.py line:~26
Comment 9 Thibault Saunier 2012-08-01 16:51:03 UTC
Created attachment 220080 [details] [review]
Add a way to specify folder where to look for overrides outside of PyGobject
Comment 10 Simon Feltman 2012-08-01 19:13:30 UTC
All that should be needed is to put:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

Into gi/__init__.py as well as inside of the distributed overrides package __init__.py. For instance:

..../GStreamer/
  gi/
    __ini__.py  <-- pkgutil stuff in here
    overrides/
       __init__.py
       Gst.py

Then you can use pythons standard path mechanisms (Adding GStreamer to PYTHONPATH or installing it to site-packages)
Comment 11 Simon Feltman 2012-08-01 19:26:53 UTC
Upon a little further testing the pkgutil code will also need to live in:
gi/overrides/__init__.py in both the pygobject and the override package.
Comment 12 Thibault Saunier 2012-08-01 20:51:26 UTC
That's pretty neat, also we will need the override (in overrides/__init__) function to be public it seems.

Also wanted to make sure that I am rigth to say that some glue code in C is needed (basically a call to pyg_register_gtype_custom) to use newly define fundamental types? If it is the case I think we will need to revive gst-python as I don't see that being done in GStreamer itself, anyway something to discuss in the GStreamer community.
Comment 13 Simon Feltman 2012-08-01 23:09:01 UTC
I'm not sure if it is a dead end but you could look into using pythons ctypes. There are also a handful of type/registration methods exposed to python in GObject and _gobject which may also be useful.
Comment 14 Tim-Philipp Müller 2012-08-05 11:05:05 UTC
*** Bug 681217 has been marked as a duplicate of this bug. ***
Comment 15 Johan (not receiving bugmail) Dahlin 2012-08-06 12:36:07 UTC
While pygobject should not ship Gst.py, the same should happen with Gtk and others eventually, there's no need for an additional PYGOBJECT_OVERRIDE path, it'll just create confusion.

I'm strongly against the proposed patch.

Whatever package that ships Gst.py should just install it into gi/overrides. For the uninstalled case, just set PYTHONPATH / gi.__path__. The application needs to be modified anyway to support that.
Comment 16 Simon Feltman 2012-08-07 00:32:15 UTC
Hi Johan,
Are you against the usage of pkutil for path extension all together or just having a new env for package overriding (as was shown in the patch)?

pkutil.extend_path will allow PYTHONPATH to work for multiple gi/override directories (and I think it would be necessary for the uninstalled case). It definitally seems to be the cleanest approach as it allows usage of the builtin python package lookup techniques.
Comment 17 Johan (not receiving bugmail) Dahlin 2012-08-07 12:29:53 UTC
Simon: just using pkgutil would be okay I guess, if I understand correctly an actual requirement for uninstalled modules.

Adding an environment variable is pretty horrible, you'd need to convince me harder that it is actually needed. And if it's needed it should be called something terrible such as:

    PYGOBJECT_GI_OVERRIDE_UNINSTALLED_PATH
Comment 18 Thibault Saunier 2012-08-07 12:32:11 UTC
We decided it was actually not needed as we can cleanly get the same result using pkgutil.
Comment 19 Thibault Saunier 2012-08-08 20:02:05 UTC
I tried to find out how to make GstFraction working from python using the various ways described previously but didn't find any solution. The only thing that made it working is http://cgit.collabora.com/git/user/tsaunier/gst-python/commit/?id=63d865025ef3f640316d1a18edc844fb56a16df4 but it is not optimal at all fmpov.

Do you have any other suggestions? :)
Comment 20 Thibault Saunier 2012-08-08 20:04:40 UTC
Created attachment 220724 [details] [review]
Make it possible to add overrides from outside main gi.overrides

The little patch that let us have overrides from outside main gi,overrides, I tested it in an uninstalled environment with previouly pointed branch of gst-python.
Comment 21 Thibault Saunier 2012-08-09 16:39:36 UTC
Created attachment 220806 [details] [review]
Make it possible to add overrides from outside main gi.overrides
Comment 22 Thibault Saunier 2012-08-20 03:05:59 UTC
I pushed the gst-python branch could that last patch get into pygobject?
Comment 23 Martin Pitt 2012-08-20 13:51:51 UTC
Created attachment 221844 [details] [review]
test case (failing)

Thanks for this! I'm trying to test this (in the form of a test case), but it doesn't seem to work for me. I create a new tests/gi/overrides/Regress.py and verify in the test suite that this gets loaded properly. But the override is never imported (Regress._overrides_module is None), the __import__() in gi/module.py DynamicModule._load() does not find it.

Can you demonstrate how you tested this?

Thanks!
Comment 24 Simon Feltman 2012-08-21 02:21:14 UTC
Make sure you have:
tests/gi/__init__.py
tests/gi/overrides/__init__.py

Both of which include:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

From here the pygobject/tests folder needs to either be in PYTHONPATH or added to sys.path. It might be worth it to put an additional directory between tests and gi (pygobject/tests/regress/gi) so that pygobject/tests/regress can be added as a python path and the rest of the .py files in pygobject/tests/ won't be importable (unless you want that).
Comment 25 Martin Pitt 2012-08-21 06:01:21 UTC
Created attachment 221962 [details] [review]
(broken) patch and test

Ah, thanks Simon. I keep forgetting to create the __init__.py files, silly me.

I'm a little further now. As you suggested I moved the test override into a subdirectory, to avoid having the test_*.py scripts become importable.

Now it seems to find the override, but fails with:

Traceback (most recent call last):
  • File "./runtests.py", line 44 in <module>
    suite = loader.loadTestsFromNames(names)
  • File "/usr/lib/python3.2/unittest/loader.py", line 137 in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
  • File "/usr/lib/python3.2/unittest/loader.py", line 137 in <listcomp>
    suites = [self.loadTestsFromName(name, module) for name in names]
  • File "/usr/lib/python3.2/unittest/loader.py", line 96 in loadTestsFromName
    module = __import__('.'.join(parts_copy))
  • File "/home/martin/upstream/pygobject/tests/test_overrides.py", line 15 in <module>
    from gi.repository import GLib
  • File "../gi/repository/__init__.py", line 25 in <module>
    from ..importer import DynamicImporter
  • File "../gi/importer.py", line 28 in <module>
    from .module import DynamicModule, DynamicGObjectModule, DynamicGLibModule
  • File "../gi/module.py", line 38 in <module>
    from .overrides import registry
ImportError: cannot import name registry

That's with python 3.2. With python 2.7 I get

======================================================================
ERROR: test_separate_path (test_overrides.TestRegistry)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/martin/upstream/pygobject/tests/test_overrides.py", line 57, in test_separate_path
    self.assertEqual(Regress.REGRESS_OVERRIDE, 42)
  File "/home/martin/upstream/pygobject/gi/module.py", line 269, in __getattr__
    return getattr(self._introspection_module, name)
  File "/home/martin/upstream/pygobject/gi/module.py", line 115, in __getattr__
    self.__name__, name))
AttributeError: 'gi.repository.Regress' object has no attribute 'REGRESS_OVERRIDE'
Comment 26 Simon Feltman 2012-08-23 00:38:03 UTC
Review of attachment 221962 [details] [review]:

The problem is due to the order in which tests are being run. test_everything is run before test_overrides.
The issue with this is test_everything uses "from gi.repository import Regress" without the "regress" override package in sys.path yet, this causes the bare, non-overridden Regress module from gi to be cached as Regress in sys.modules. By the time test_overrides is run, it doesn't matter what is added to sys.path because the module cached in sys.modules will always be used.

This speaks to an overall problem with how the unittests are run. It seems like it would be cleaner if the test runner started a new python process for each of the "test_*" files. Otherwise, you can run into issues like this or unknown cross dependencies between the tests. For instance, running "make check TEST_NAMES=test_overrides" shows a completely different problem with this patch because test_everything is not run first. Likewise modifying PYTHONPATH will actually fix all failure cases because this adds the new regress package to sys.path at python startup time.

The problem with "make check TEST_NAMES=test_overrides" is there is code sitting at the package level of pygobject/gi/overrides/__init__.py. Which cannot be reached if "regress/gi/overrides/__init__.py" is loaded first. In large systems that utilize pkgutil, it ends up being bad practice to put code in __init__.py files because of this. sys.path ordering problems can then be avoided by limiting __init__.py to pkgutil magic.

Notice what sys.path looks like during the different runs:

Python sys.path for test_overrides during "make check TEST_NAMES=test_overrides"
['regress', '../', '/home/simon/src/gnome3/pygobject/tests', ...]

Python sys.path for test_overrides during "make check": 
['regress', '../', '../', '../', '/home/simon/src/gnome3/pygobject/tests', ...]

The "tests" folder is already in sys.path so my previous recommendation of using a "regress" sub-folder as a cleaner setup doesn't even matter. This is being set through PYTHONPATH by Makefile.in

This long winded explanation actually has a simple fix. We can remove the regress sub-folder and rely on the tests folder already being in sys.path, or append it to sys.path. In general, setup of sys.path should probably be limited to runtests.py or the makefile setting PYTHON path, otherwise you can get a fairly mucky sys.path by the end of the test suite (notice the multiple '../' entries when running with "make check" and this was only after a few tests ran).

::: gi/overrides/__init__.py
@@ +5,3 @@
+# support overrides in different directories than our gi module
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)

This should probably go before any non-system imports.

::: tests/test_overrides.py
@@ +7,3 @@
 sys.path.insert(0, "../")
+# also catch tests/regress/gi/overrides/
+sys.path.insert(0, "regress")

We should move all modification to sys.path to runtests.py or Makefile.in which modifies PYTHONPATH. Also "regress" path should be appended or at least be ordered after "../" so code in pygobject/gi/overrides/__init__.py is ensured to be available.
Comment 27 Simon Feltman 2012-08-23 01:17:43 UTC
Created attachment 222201 [details] [review]
sys.path cleanup

Additionally cleaned up sys.path entries, removing all occurances of "../" from being added. This is now setup once by runtests.py.
Also moved setting of PYTHONPATH to ["pygobject/tests",...] into runtests.py.

The only issues I see is if there are people relying on the potential ability to use:
 python tests/test_everything.py
vs:
 make check TEST_NAMES=test_everything

The "../" was only added to a few files: test_everything, test_overrides, test_gdbus, and test_pygtkcompat. So all of the other test_modules were actually relying on one of these to run first in order for pygobject/ to get added to sys.path.
Comment 28 Martin Pitt 2012-08-23 04:13:40 UTC
Thanks Simon for the analysis!

I fully agree that the sys.path handling in tests/ is confusing and impractical. As it happens, I also was about to drop these for an entirely different reason: I want to change the tests so that they can also run against the system installed libraries.

I separated this part of the change and committed it as

http://git.gnome.org/browse/pygobject/commit/?id=c7c95a795eee499373499ea5b771447746317bfb

After that, I cleaned up the patch for this actual bug, tests work fine now:

http://git.gnome.org/browse/pygobject/commit/?id=b1a9848a7a7255e6b1ccd98712dd62b1514078b9

Thanks to everyone!
Comment 29 Paolo Borelli 2012-08-23 09:17:09 UTC
The initial comment also mentioned changes to the conversion script, if there are any, feel free to open a new bug with a patch. I do not see any problems in including those.
Comment 30 Simon Feltman 2012-09-29 10:00:25 UTC
*** Bug 660647 has been marked as a duplicate of this bug. ***
Comment 31 Adam Dingle 2013-05-25 23:55:18 UTC
Like the original poster, I'd like to be able to install a program with overrides in a local directory.  So I was happy to see that a fix for this request was committed last year.  But after struggling to get this to work I'm unconvinced that the fix here is practical.

The essential problem is that it seems to require that the overriding directory come *after* the directory where pygobject is installed in sys.path.  At least, the unit test in pyobject/tests/gi/overrides works that way; the setup code in pygobject/tests/runtest.py contains this:

  # we have to do this here instead of Makefile.am so that the implicitly added
  # directory for the source file comes after the builddir
  sys.path.insert(0, tests_builddir)
  sys.path.insert(0, builddir)

But as far as I can tell there's no practical way that I can set up environment variables or configuration files so that a directory with my local overrides comes after the main Python package dir (e.g. /usr/lib/python3/site-packages) in sys.path on a typical system.  I've tried various tricks and they all fail:

- A directory in PYTHONPATH is prepended, not appended, to sys.path.

- I tried setting PYTHONSTARTUP=~/.pythonrc, and in .pythonrc I called sys.path.append().  But it turns out that .pythonrc will run only in interactive sessions, and not when Python is embedded by a program.

- My system's built-in sys.path includes an element ~/.local/lib/python3.3/site-packages but that precedes the main site-packages dir.  I can put a .pth file in this local directory, but any path I list there will be inserted into the path, again preceding the main site-packages dir.

- On my system PYTHONPATH is empty when I log in, so I can't append to it by putting something like PYTHONPATH=$PYTHONPATH:~/local/dir in .gnomerc or a similar file.

- In theory I could set PYTHONPATH=/usr/lib/python3/site-packages:~/local/dir to force the main site-packages dir to come before mine.  But then both of those would be prepended to the other predefined path elements, changing the ordering of /usr/lib/python3/site-packages relative to the other built-in path elements.  That's dangerous (and in fact led to breakage when I tried it on my system).

In addition, it's slightly inconvenient to have to create two separate __init.py__ files in my local overrides directory with the pkgutil magic listed above.  Not a huge deal, but it would be really nice if I could simply configure packages with --prefix=~/local, build and have them Just Work if I set an environment variable correctly.

Anyway, I'm far from being a Python guru, so maybe there's something I've missed here.  Have others been able to get locally installed overrides working with the fix committed here?