[GRASS-dev] [GRASS GIS] #3772: Make the grass library importable outside of a GRASS session

GRASS GIS trac at osgeo.org
Tue Mar 5 06:35:09 PST 2019


#3772: Make the grass library importable outside of a GRASS session
-------------------------+-------------------------
 Reporter:  pmav99       |      Owner:  grass-dev@…
     Type:  enhancement  |     Status:  new
 Priority:  normal       |  Milestone:
Component:  Python       |    Version:  svn-trunk
 Keywords:               |        CPU:  Unspecified
 Platform:  Unspecified  |
-------------------------+-------------------------
 == Note

 This came out rather lengthy. Before we get started, I should say that
 comments and feedback are more than welcome.

 == Objective

 The grass library is not currently importable outside of a GRASS session.
 This is easily demonstratable with this:

 {{{
 $ PYTHONPATH=dist.x86_64-pc-linux-gnu/etc/python python -c 'import
 grass.script; print("OK")'

 Traceback (most recent call last):
   File "<string>", line 1, in <module>
   File "/home/feanor/Prog/git/grass-p2/repo/dist.x86_64-pc-linux-
 gnu/etc/python/grass/script/__init__.py", line 5, in <module>
     from .core   import *
   File "/home/feanor/Prog/git/grass-p2/repo/dist.x86_64-pc-linux-
 gnu/etc/python/grass/script/core.py", line 38, in <module>
     gettext.install('grasslibs', os.path.join(os.getenv("GISBASE"),
 'locale'))
   File
 "/home/feanor/Prog/git/grass-p2/.direnv/python-2.7.15/lib/python2.7/posixpath.py",
 line 70, in join
     elif path == '' or path.endswith('/'):
 AttributeError: 'NoneType' object has no attribute 'endswith'
 }}}

 The error is pretty much obvious. {{{$GISBASE}}} is not defined and
 consequently the
 {{{os.getenv()}}} call returns {{{None}}} instead of the path to the GRASS
 distribution.

 IMHO having an importable library will be significantly beneficial both to
 GRASS devs and
 to those who want to script GRASS since, among other things, it gives you
 the ability
 to programmatically create a GRASS session without having to jump through
 hoops.

 So I decided to have a look.

 == tl;dr

 Making this work on Linux is not too hard but it needs
 feedback/testing on Win and Mac which unfortunately I cannot do.

 == Necessary note about gettext

 When you are developing internationalized Python applications
 the very first thing you usually need to do is to call
 `gettext.install()`.
 This function call, injects {{{_()}}} into the builtins namespace,
 effectively
 making it globally available in the codebase.

 If you do this at the very top of your package's {{{__init__.py}}} you
 then
 don't need to do anything else.

 == What is the problem?

 There is nothing really preventing us to import the GRASS library apart
 from this line:

 {{{
 gettext.install('grasslibs', os.path.join(os.getenv("GISBASE"), 'locale'))
 }}}

 == How to fix?

 {{{$GISBASE}}} is the (absolute?) path to the GRASS
 installation/distribution directory.
 The python library is located at {{{$GISBASE/etc/python/grass}}}
 while the {{{locale}}} directory is located at {{{$GISBASE/locale}}}.

 All we need to do to get this working is to get rid of the {{{os.getenv}}}
 call and replace it with a relative path to the {{{locale}}} directory.

 Assumming that the directory structure of a GRASS distribution
  is stable we can easily do this using {{{os.path.dirname}}} and
 {{{__file___}}}.
 E.g. by using something like this:

 {{{
 # contents of lib/python/__init__.py
 import gettext
 import os

 from os.path import dirname

 # _ROOT_DIR points to the root directory of the GRASS
 installation/distribution
 # Yeap, calling 4 times dirname is not really elegant, but we want to go
 from:
 #     dist.x86_64-pc-linux-gnu/etc/python/grass/__init__.py
 # to:
 #     dist.x86_64-pc-linux-gnu/
 #
 _ROOT_DIR = dirname(dirname(dirname(dirname(os.path.abspath(__file__)))))

 # Setup i18N
 #
 # Calling `gettext.install()` installs `_()` in the builtins namespace and
 thus it
 # becomes available globally (i.e. in the same process). We need to do
 this here
 # to ensure that the injection happens before anything else gets imported.
 #
 # For more info please check the following links:
 # - https://docs.python.org/2/library/gettext.html#gettext.install
 # - https://pymotw.com/2//gettext/index.html#application-vs-module-
 localization
 # - https://www.wefearchange.org/2012/06/the-right-way-to-
 internationalize-your.html
 #
 _LOCALE_DIR = os.path.join(_ROOT_DIR, "locale")

 # XXX not really necessary + it will fail when GRASS is compiled without
 NLS
 # XXX but it does make debugging easier.
 if not os.path.exists(_LOCALE_DIR):
     raise ValueError("LOCALE could not be found: %s" % _LOCALE_DIR)

 gettext.install('grasslibs', _LOCALE_DIR)
 gettext.install('grassmods', _LOCALE_DIR)
 gettext.install('grasswxpy', _LOCALE_DIR)

 # ...
 }}}

 After doing that, any code that uses {{{import grass}}} will be able to
 use {{{_()}}}
 and, consequently, the various {{{gettext.install()}}} calls throughout
 the codebase
 are no longer needed. The only exception to that is
 {{{lib/init/grass.py}}} since it
 does not import the {{{grass}}} library.

 == WRT to Win and Mac

 No idea if this works OK on Win & Mac.
 I would appreciate any feedback from users of those platforms.

 == WRT to Python 2 and Python 3 compat

 I have not extensively tested this yet.
 But if there is any boilerplace code needed for Python 2/3
 compatibility like e.g. the one introduced here:
 https://trac.osgeo.org/grass/browser/grass/trunk/gui/wxpython/core/globalvar.py?rev=73930#L40
 then this code should also be moved to {{{lib/python/__init__.py}}}

 == Show me the code!

 The code is here (branch "importable"): https://github.com/pmav99/grass-
 ci/tree/importable

 There are only two commits currently, but more may be added.
 Be warned, this is WIP so I will be rebasing this branch.

 == How to test

 The quick test is of course:

 {{{
 $ PYTHONPATH=dist.x86_64-pc-linux-gnu/etc/python python -c 'import
 grass.script; print("OK")'
 }}}

 which should now work just fine. If you can run any tests and report any
 issues I would
 be really grateful.

-- 
Ticket URL: <https://trac.osgeo.org/grass/ticket/3772>
GRASS GIS <https://grass.osgeo.org>



More information about the grass-dev mailing list