[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