OSGeo / grass

GRASS GIS - free and open-source geospatial processing engine
https://grass.osgeo.org
Other
827 stars 301 forks source link

[Bug] python script.setup issues related to message translation #2515

Open tpilz opened 2 years ago

tpilz commented 2 years ago

Describe the bug

I want to use GRASS in a python module but get some errors when using certain functions of the grass.script.setup module.

To Reproduce

Connect to a grass mapset in python as described in the docs, e.g.

import grass.script.setup as gsetup
rcfile = gsetup.init('my_db', 'my_location', 'my_mapset')
rcfile.finish()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/grass82/etc/python/grass/script/setup.py", line 415, in finish
    finish()
  File "/usr/local/grass82/etc/python/grass/script/setup.py", line 474, in finish
    clean_default_db()
  File "/usr/local/grass82/etc/python/grass/script/setup.py", line 434, in clean_default_db
    gcore.message(_("Cleaning up default sqlite database ..."))
TypeError: 'str' object is not callable

System description (please complete the following information):

Tested on two Linux systems with GRASS 7.8 and GRASS 8.2 using python 3.6.2 and 3.9.10, respectively.

Additional context

Error is caused by gcore.message(_("<message>")) calls in setup.py. I think #551 is related where the issue was discussed but never really solved.

For the time being a workaround works for my case with gutils.try_remove(os.environ["GISRC"]) which is basically what I need here.

wenzeslaus commented 2 years ago

This usually happens with an interactive interpreter. The following works for me in Bash on Linux with compiled 8.3 from main (it should be the same in 8.2):

PYTHONPATH="$(grass --config python_path):$PYTHONPATH" LD_LIBRARY_PATH="$(grass --config path)/lib:$LD_LIBRARY_PATH" python
Python 3.8.10 (default, Jun 22 2022, 20:18:18) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import grass.script.setup as gsetup
>>> session = gsetup.init("~/grassdata/nc_spm_08_grass7/user1/")
>>> session.finish()

However, doing some computation and not storing it in the variable breaks things:

>>> "a" * 3  # Anything which stores a result.
'aaa'
>>> session = gsetup.init("~/grassdata/nc_spm_08_grass7/user1/")
>>> session.finish()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../dist.x86_64-pc-linux-gnu/etc/python/grass/script/setup.py", line 416, in finish
    finish(start_time=self._start_time)
  File ".../dist.x86_64-pc-linux-gnu/etc/python/grass/script/setup.py", line 501, in finish
    clean_temp()
  File ".../dist.x86_64-pc-linux-gnu/etc/python/grass/script/setup.py", line 477, in clean_temp
    gs.verbose(_("Cleaning up temporary files..."))
TypeError: 'str' object is not callable
>>> session = gsetup.init("~/grassdata/nc_spm_08_grass7/user1/")
>>> 42  # Anything which stores a result.
42
>>> session.finish()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../dist.x86_64-pc-linux-gnu/etc/python/grass/script/setup.py", line 416, in finish
    finish(start_time=self._start_time)
  File ".../dist.x86_64-pc-linux-gnu/etc/python/grass/script/setup.py", line 501, in finish
    clean_temp()
  File ".../dist.x86_64-pc-linux-gnu/etc/python/grass/script/setup.py", line 477, in clean_temp
    gs.verbose(_("Cleaning up temporary files..."))
TypeError: 'int' object is not callable

Workaround

Not using an interactive session should resolve the problem. The workaround for an interactive session is to restart it and always assign things to variables. This works:

>>> a = 42  # Doing computation, but storing the result in a variable.
>>> import grass.script.setup as gsetup
>>> session = gsetup.init("~/grassdata/nc_spm_08_grass7/user1/")
>>> session.finish()

Explanation

What happens is that interactive Python interpreter stores (automatically assigns) the last not assigned result to a variable called _:

>>> a = 42  # Standard assignment
>>> _  # This fails, there is no such variable.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_' is not defined
>>> 42  # No explicit assignment
42
>>> _  # This variable now exists.
42

Underscore is also name used for the translation function in GRASS GIS and that function is added globally as an additional build-in which then conflicts with the assignment done by the interactive interpreter.

The ultimate fix in GRASS GIS is to explicitly import the translation function where it is needed instead of adding a build-in.

tpilz commented 2 years ago

Thanks for the explanation and workaround!

I was actually using and initialising GRASS inside a python module and got the error when using that. Can't tell which other side-effects then might have caused the interference with the _ variable.

Anyway, my small workaround is sufficient now and it seems it is even not too complicated to fix it in GRASS.

wenzeslaus commented 2 years ago

...which other side-effects then might have caused the interference with the _ variable.

doctest are using _ and definitively interfere too, although there is a workaround for that too, search for "doctest workaround" in GRASS GIS source code. Running your code through an execute function in an IDE may run run it in an interactive console, too.

...it seems it is even not too complicated to fix it in GRASS.

Yes, there is a lot of other pieces in place making the change easier than it was in the past (one place for initialization, lazy loading, code checks). I would be happy to review a PR for this.