-
-
Notifications
You must be signed in to change notification settings - Fork 327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
grass.script.setup: Add return of a context manager from init #1912
grass.script.setup: Add return of a context manager from init #1912
Conversation
Turned init into a class with __enter__ and __exit__ methods. Had to remove output. Implemented it this way to have maximum compatibility with current implementations. (should work the same unless the implementation used the return value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @albertoparadisllop. I'm afraid I agree that this is a weird solution. I think there are couple of changes which will make it better.
I'm not 100% convinced about that, but it seems to me that a good approach given the current code is to return a "session object" from the init
function. See #1834 where I do something similar to what is needed here. Would a new class named, e.g., SessionHandle, which would be the context manager work?
Having also the manual open-close approach would be good to have. If we keep init
usable as before, this may simplify things for many people and we don't need to try to push this to 8.0. In other words, keeping backwards compatibility would be very useful.
Additionally, I merged #1829, so it is probably easiest for you to start fresh from the main branch and open a new PR (unless you want to get into some Git tricks). The new code explains the return value or that we can/will change it.
Added session object to setup to use with context manager, and open() close() methods.
I replaced the previous changes with a separate class. This way the old The class, Both And finally, I considered adding a boolean |
Renamed to class name suggested in PR comment.
I made further changes to the PR. Overall, I modeled the context manger to look like the open function in Python, so the original init is now like open. It seems to me like a good idea to follow an example of a standard Python function and it keeps the focus on the init function which keeps the overall extend of changes limited.
I kept the class, but it is simpler, more limited in what it can be used for. Specifically, it does not have open nor an init function which would start the session.
No need to keep it exactly the same. The warning for changing return value was there exactly for this purpose. Now, the new code actually keep the standalone init function in the center of the interface.
The new approach relies on the standalone init to the "opening", so the init function of the handle and the enter functions do very little work. There is no open function anymore. I changed the open-close naming to init-active-finish which just keeps whatever is already there, but I think some improvements would be useful there.
No need to be similar to init, so I tried to be similar to open, so enter returns self, so
That seemed good. The enter now checks that, but there is no open which needs to check it anymore.
Using
That's actually much bigger question. Maybe it is best not to address that in this PR. @albertoparadisllop I'm sorry I was not able to continue in this in October. |
…set path and auto-detection of GRASS runtime
Here are some possible usages. All assume something like: sys.path.append(
subprocess.check_output(["grass", "--config", "python_path"], text=True).strip()
)
import grass.script as gs
import grass.script.setup Good casesExplicit init-finishsession = gs.setup.init("~/grassdata/nc_basic_spm_grass7/user1")
gs.run_command("g.region", flags="p")
session.finish() The old way still works: gs.setup.init("~/grassdata/nc_basic_spm_grass7/user1")
gs.run_command("g.region", flags="p")
gs.setup.finish() Context managerContext manager: with gs.setup.init("~/grassdata/nc_basic_spm_grass7/user1"):
gs.run_command("g.region", flags="p") The above shows that the newly introduced class is not intrusive and can be quite hidden. However, you can use the context manager and keep the value (this might be useful later when the class is more powerful): with gs.setup.init("~/grassdata/nc_basic_spm_grass7/user1") as session:
print(session)
gs.run_command("g.region", flags="p") Separate creation and context manage use like with Python's open. This is probably not slightly dangerous, so not great, but flexible. (Needed to allow for this to work also the old way, i.e., without context manager like Python's open does.) session = gs.setup.init("~/nc_basic_spm_grass7/user1")
with session:
print(session)
gs.run_command("g.region", flags="p") Bad casesNo cleaning happensYou forget to call finish or use the gs.setup.init("~/grassdata/nc_basic_spm_grass7/user1")
gs.run_command("g.region", flags="p") Similarly to the Python's open, the session is not finished. This could be prevented by creating an instance of the class and calling Raises exceptionUse twice with
|
This PR now focuses on implementing the context manger to automatically close the session as asked for in #1848. However, it might be useful to compare it with #1834 focused on solving the issue for Jupyter notebooks. #1834 takes a different approach focusing on usage in notebooks where context mangers are not useful for a session active through the whole notebook. The context manager is replaced in #1834 by finalizer approach (weakref.finalize), so cleaning happens automatically but in somewhat hidden fashion (when the object is garbage collected). Another difference is that #1834 uses one global object (one reference kept as a global attribute) which is sometimes recycled for subsequent grass.jupyter.setup.init calls (much more common in notebooks). The global object is needed to ensure that there is always a reference to the session otherwise the session can be finished anytime (when the object is garbage collected). The full explanation is in 45c1e7f. |
This is now ready for review and it includes latest updates from main for better testing and comparison. Here is a binder link. @ninsbl Since you originally suggested this (#1848), can you please check this out? @albertoparadisllop Feel free to comment. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I remove myself from review, it shows my old review, so I'm leaving this comment to make my review neutral.
Edit: Well, that didn't work, but at the bottom, there is a dismiss review button which turns it into a neutral review (review comment).
Now I tested the binder link. This looks very good to me. Very elegant. Great work. |
Thanks!
Definitively desired and a different PR. Feel free to open an issue/PR. For now, a good workaround is to use the grass executable through the subprocess package (the same works well for R too) or do some hacks to access the grass.script.create_location function (see e.g. test added in this PR). |
The grass.script.setup.init function now returns a SessionHandle handle object which is a context manager (a class with __enter__ and __exit__ methods). Works as the Python open function which can be used without a context manager. This also makes it compatible with the current usage of init. Supports cases when session object is used to do finish and when reference to the session object is not assigned to variable. Use of global finish is still supported. Multiple calls of finish on session raises exception. Multiple sessions in parallel are not supported (the underlying global finish function does not currently support that). Cleaning is not enforced. However, the context manager makes it easier to do that right. A simple test using pytest of context manager API uses private (protected) function from grass.script.core (for now). Co-authored-by: Vaclav Petras <wenzeslaus@gmail.com>
The grass.script.setup.init function now returns a SessionHandle handle object which is a context manager (a class with __enter__ and __exit__ methods). Works as the Python open function which can be used without a context manager. This also makes it compatible with the current usage of init. Supports cases when session object is used to do finish and when reference to the session object is not assigned to variable. Use of global finish is still supported. Multiple calls of finish on session raises exception. Multiple sessions in parallel are not supported (the underlying global finish function does not currently support that). Cleaning is not enforced. However, the context manager makes it easier to do that right. A simple test using pytest of context manager API uses private (protected) function from grass.script.core (for now). Co-authored-by: Vaclav Petras <wenzeslaus@gmail.com>
The grass.script.setup.init function now returns a SessionHandle handle object which is a context manager (a class with __enter__ and __exit__ methods). Works as the Python open function which can be used without a context manager. This also makes it compatible with the current usage of init. Supports cases when session object is used to do finish and when reference to the session object is not assigned to variable. Use of global finish is still supported. Multiple calls of finish on session raises exception. Multiple sessions in parallel are not supported (the underlying global finish function does not currently support that). Cleaning is not enforced. However, the context manager makes it easier to do that right. A simple test using pytest of context manager API uses private (protected) function from grass.script.core (for now). Co-authored-by: Vaclav Petras <wenzeslaus@gmail.com>
Issue #1848
Turnedinit
function into a class with__enter__
and__exit__
methods. Had to remove output.I think its a bit of a weird solution, but this implementation allows for it to be used as a context manager, and as it has been used in the past (unless the return value from the function was used)."Normal" use, almost same as before:As a context manager:I tried to implement this withcontextlib.contextmanager
, but that would only allow it to be used as a context manager exclusively. Ifcontextlib.contextmanager
is the preferred solution, it'd have to be split into two separate functions, one for use as a context manager, and one as it has been used in the past.Another approach could be to instead have the class use an
open()
andclose()
functions, and simply have the context manager call those automatically. This would mean they would have to be called explicitly in non-context-manager uses though.Changed to use this approach. Created a
SessionHandle
class to be used as context manager, and withopen()
andclose()
methods.init
not changed to ensure backwards compatibility.I'm a beginner, so any feedback is welcome. Done for Hacktoberfest.