-
-
Notifications
You must be signed in to change notification settings - Fork 505
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
Setting default redirections for all commands #269
Comments
Hi Lucas. I global configuration sounds useful In theory, a global configuration is currently possible: import sh
sh.Command._call_args["timeout"] = 1
sh.sleep(3) This is ugly though because it's manipulating a clearly private variable. Others have suggested in the past that we could do some kind of configuration object: from sh import Config
sh2 = Config(timeout=1)
sh2.sleep(3) I think both ways can be acceptable. In the case of the former way, we should rename What do you think about implementing a configuration class? We would also need to add a section to the docs about "Global Configuration" that demonstrates how to use it. And of course tests :) |
Sounds like a plan ! What about defining a module global constant
We could implement a configuration class, but if its sole purpose is to provide an access to this global dict of defaults, I'm not sure it's really necessary. I vote for KISS & YAGNI :) |
After a short code review, it looks to me that
Hence it looks like currently the defaults are only taken into account at command invocation time, not at command import time.
A solution could be to make |
Actually, the more I'm thinking about it, the less I like mutating the global config dictionary. I could see the case where this bites people: import sh
sh.defaults["timeout"] = 1
# ... lots of lines later, in a different module ...
# why does this keep dying!?!?!?!?!
stuff = sh.cat("/tmp/some-large-file") I'm not sure that the shared state drawbacks are worth the benefits. A configuration class, on the other hand, is more explicit about the state it contains. When you use it, you know you are launching a command with modified defaults, and those defaults only apply to the commands that are launched from it, not globally. |
Ok, then what would you think of something like that:
I'd really prefer not to have to prefix all my commands by |
Looks good Lucas. If we can make both |
Perfect ! I'll start working on this asap |
A few thoughts after looking at the code. First, what we planned is actually currently possible somehow !
This is thanks to Now, the only issue that remain, is the matter of module isolation.
and another another file
And when running The main blocker to achieve this is Python module management logic. Do you thing it's a problem worth investigating ? |
That's great news. I had forgotten all about that and it's not in the docs. Re: isolation. I think we will need to create a new module object in |
The more I think about it, the more I'm convinced it's probably not worth adding this kind of isolation, if we stick to the current usage. In the end this planned usage:
Is not really such an improvment over:
If you are OK, I'm simply going to at this to the documentation. |
sounds good |
Here is a first pull request implementing a solution to this issue. |
Hi. Any update on this ? |
Updated the PR. After reviewing this comment thread, I'm not sure we are on the same page with regards to state isolation. For any changes going forward, I would like to keep all state management explicit, for example, if you change global defaults, they should be global only in the context of an object that we launch commands from: import sh, sys
shell_with_defaults = sh(_out=sys.stdout)
from shell_with_defaults import ls Or, if a context manager was used... import sh, sys
with sh.defaults(_out=sys.stdout) as sh2:
from sh2 import ls |
I understand. But I think we simply cannot |
Modules don't need to be backed by any physical file. There are some cool you can do with the import system, for example, importing a module from over the network. We will just need to dynamically create a module object and register it correctly with |
Ok, great ! |
Any update on this ? :) |
import sys
from types import ModuleType
from contextlib import contextmanager
from uuid import uuid4
import inspect
def fake_module():
name = "sh-" + uuid4().hex
mod = ModuleType(name)
mod.thing = "i'm dynamically generated in module %s" % mod.__name__
return mod
@contextmanager
def module_context():
mod = fake_module()
sys.modules[mod.__name__] = mod
try:
yield mod
finally:
del sys.modules[mod.__name__]
del mod
class ShModuleHook(object):
def find_module(self, fullname, path=None):
parent_frame = inspect.stack()[-1][0]
module = parent_frame.f_locals.get(fullname, None)
loader = None
if module:
loader = self
return loader
def load_module(self, fullname):
parent_frame = inspect.stack()[-1][0]
module = parent_frame.f_locals.get(fullname, None)
return module
sys.meta_path = [ShModuleHook()]
with module_context() as sh2:
from sh2 import thing
print(thing) |
I should probably add that it's a little crazy, but nothing that I'm not ok with. |
Thanks a lot for this code snippet ! Based on this trick, I updated a new version of my PR.
Tell me what you think of it :) |
I have updated the PR. The feature is fully implemented, with all the tests passing, and just one notable limitation: Because of Python module caching system, using I tried to find a way around this limitation, but Given we document this in the docs, I think this PR is ready to be merged. |
this is in the |
* added `_out` and `_out_bufsize` validator [#346](amoffat/sh#346) * bugfix for internal stdout thread running when it shouldn't [#346](amoffat/sh#346) * regression bugfix on timeout [#344](amoffat/sh#344) * regression bugfix on `_ok_code=None` * further improvements on cpu usage * regression in cpu usage [#339](amoffat/sh#339) * fd leak regression and fix for flawed fd leak detection test [#337](amoffat/sh#337) * support for `io.StringIO` in python2 * added support for using raw file descriptors for `_in`, `_out`, and `_err` * removed `.close()`ing `_out` handler if FIFO detected * composed commands no longer propagate `_bg` * better support for using `sys.stdin` and `sys.stdout` for `_in` and `_out` * bugfix where `which()` would not stop searching at the first valid executable found in PATH * added `_long_prefix` for programs whose long arguments start with something other than `--` [#278](amoffat/sh#278) * added `_log_msg` for advanced configuration of log message [#311](amoffat/sh#311) * added `sh.contrib.sudo` * added `_arg_preprocess` for advanced command wrapping * alter callable `_in` arguments to signify completion with falsy chunk * bugfix where pipes passed into `_out` or `_err` were not flushed on process end [#252](amoffat/sh#252) * deprecated `with sh.args(**kwargs)` in favor of `sh2 = sh(**kwargs)` * made `sh.pushd` thread safe * added `.kill_group()` and `.signal_group()` methods for better process control [#237](amoffat/sh#237) * added `new_session` special keyword argument for controlling spawned process session [#266](amoffat/sh#266) * bugfix better handling for EINTR on system calls [#292](amoffat/sh#292) * bugfix where with-contexts were not threadsafe [#247](amoffat/sh#195) * `_uid` new special keyword param for specifying the user id of the process [#133](amoffat/sh#133) * bugfix where exceptions were swallowed by processes that weren't waited on [#309](amoffat/sh#309) * bugfix where processes that dupd their stdout/stderr to a long running child process would cause sh to hang [#310](amoffat/sh#310) * improved logging output [#323](amoffat/sh#323) * bugfix for python3+ where binary data was passed into a process's stdin [#325](amoffat/sh#325) * Introduced execution contexts which allow baking of common special keyword arguments into all commands [#269](amoffat/sh#269) * `Command` and `which` now can take an optional `paths` parameter which specifies the search paths [#226](amoffat/sh#226) * `_preexec_fn` option for executing a function after the child process forks but before it execs [#260](amoffat/sh#260) * `_fg` reintroduced, with limited functionality. hurrah! [#92](amoffat/sh#92) * bugfix where a command would block if passed a fd for stdin that wasn't yet ready to read [#253](amoffat/sh#253) * `_long_sep` can now take `None` which splits the long form arguments into individual arguments [#258](amoffat/sh#258) * making `_piped` perform "direct" piping by default (linking fds together). this fixes memory problems [#270](amoffat/sh#270) * bugfix where calling `next()` on an iterable process that has raised `StopIteration`, hangs [#273](amoffat/sh#273) * `sh.cd` called with no arguments no changes into the user's home directory, like native `cd` [#275](amoffat/sh#275) * `sh.glob` removed entirely. the rationale is correctness over hand-holding. [#279](amoffat/sh#279) * added `_truncate_exc`, defaulting to `True`, which tells our exceptions to truncate output. * bugfix for exceptions whose messages contained unicode * `_done` callback no longer assumes you want your command put in the background. * `_done` callback is now called asynchronously in a separate thread. * `_done` callback is called regardless of exception, which is necessary in order to release held resources, for example a process pool
Hello,
First, a big THANK YOU for your work on this project.
I find it extremly useful, simple and pythonic.
And it's going to be extremely useful to convince my coworkers to migrate their shell scripts to Python :)
Now, would you be open to allowing some kind of global configuration, specifically on the default redirection for
stderr
?Currently, some "hardcoded" defaults are defined there:
https://github.com/amoffat/sh/blob/master/sh.py#L669
And by default the commands
stderr
is simply discarded.Could that default behaviour be somehow configurable ?
I would like the commands
stderr
to be written to the parent Python scriptstderr
by default.And in the same spirit, it could be sometimes handy to forward
stdout
the same way.I'd love to work on a pull request if you are ok with this feature request.
Regards
The text was updated successfully, but these errors were encountered: