generated from datalad/datalad-extension-template
-
Notifications
You must be signed in to change notification settings - Fork 10
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
Add PersistentSubShell
-feature
#596
Merged
Merged
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
4261fdf
add `bufsize` parameter to iterable subprocesses
christian-monch f592614
improve `align_pattern` performance
christian-monch 025c18a
add `datalad_next.shell.shell` context manager
christian-monch 68c22af
[temp] add safe_read for upload
christian-monch d57f533
add `safe_read` wrapper to fix a bug
christian-monch 45c5fac
add progress callbacks for download/upload
christian-monch 81d8554
update docstring
christian-monch 6cb2b7c
raise CommandError if shell commands fail
christian-monch 2b6917c
fix type definitions
christian-monch c5c776e
increase test coverage of shell-code
christian-monch c1b964a
support `str`-commands in ShellCommandExecutor
christian-monch ee4bdf5
clean up code and improve names
christian-monch 950ec40
fix an error in `str`-command handling
christian-monch f005419
fixed a typo
christian-monch b849323
use a queue to signal completion in `posix.upload`
christian-monch 1f34132
replace `stat` with `ls` in download-op
christian-monch 620bbe1
fix a comment
christian-monch 028e752
add a -currently unused- credential argument
christian-monch 9d4ae1a
fix information in comments
christian-monch d006050
address reviewer comments
christian-monch 1a99586
update examples in docstrings
christian-monch ba8985e
update documentation
christian-monch e68aa0d
improve doc-strings, remove unused code
christian-monch 7b5635e
Employ `shell` feature in SSH test fixture
mih c1a2dcf
adapt mih's changes to changed code
christian-monch 2117d65
fix a typo
christian-monch 04b4140
fix escape in docstrings
christian-monch bcb39b8
fix type issues and a comment
christian-monch 79436f8
fix windows tests
christian-monch ce445a8
Update datalad_next/shell/operations/posix.py
christian-monch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
"""A persistent shell connection | ||
|
||
This module provides a context manager that establishes a connection to a shell | ||
and can be used to execute multiple commands in that shell. Shells are usually | ||
remote shells, e.g. connected via an ``ssh``-client, but local shells like | ||
``zsh``, ``bash`` or ``PowerShell`` can also be used. | ||
|
||
The context manager returns an instance of :class:`ShellCommandExecutor` that | ||
can be used to execute commands in the shell via the method | ||
:meth:`ShellCommandExecutor.__call__`. The method will return an instance of | ||
a subclass of :class:`ShellCommandResponseGenerator` that can be used to | ||
retrieve the output of the command, the result code of the command, and the | ||
stderr-output of the command. | ||
|
||
Every response generator expects a certain output structure. It is responsible | ||
for ensuring that the output structure is generated. To this end every | ||
response generator provides a method | ||
:meth:`ShellCommandResponseGenerator.get_command_list`. The method | ||
:class:`ShellCommandExecutor.__call__` will pass the user-provided command to | ||
:meth:`ShellCommandResponseGenerator.get_command_list` and receive a list of | ||
final commands that should be executed in the connected shell and that will | ||
generate the expected output structure. Instances of | ||
:class:`ShellCommandResponseGenerator` have therefore four tasks: | ||
|
||
1. Create a final command list that is used to execute the user provided | ||
command. This could, for example, execute the command, print an | ||
end marker, and print the return code of the command. | ||
|
||
2. Parse the output of the command, yield it to the user. | ||
|
||
3. Read the return code and provide it to the user. | ||
|
||
4. Provide stderr-output to the user. | ||
|
||
A very versatile example of a response generator is the class | ||
:class:`VariableLengthResponseGenerator`. It can be used to execute a command | ||
that will result in an output of unknown length, e.g. ``ls``, and will yield | ||
the output of the command to the user. It does that by using a random | ||
*end marker* to detect the end of the output and read the trailing return code. | ||
This is suitable for almost all commands. | ||
|
||
If :class:`VariableLengthResponseGenerator` is so versatile, why not just | ||
implement its functionality in :class:`ShellCommandExecutor`? There are two | ||
major reasons for that: | ||
|
||
1. Although the :class:`VariableLengthResponseGenerator` is very versatile, | ||
it is not the most efficient implementation for commands that produce large | ||
amounts of output. In addition, there is also a minimal risk that the end | ||
marker is part of the output of the command, which would trip up the response | ||
generator. Putting response generation into a separate class allows to | ||
implement specific operations more efficiently and more safely. | ||
For example, | ||
:class:`DownloadResponseGenerator` implements the download of files. It | ||
takes a remote file name as user "command" and creates a final command list | ||
that emits the length of the file, a newline, the file content, a return | ||
code, and a newline. This allows :class:`DownloadResponseGenerator` | ||
to parse the output without relying on an end marker, thus increasing | ||
efficiency and safety | ||
|
||
2. Factoring out the response generation creates an interface that can be used | ||
to support the syntax of different shells and the difference in command | ||
names and options in different operating systems. For example, the response | ||
generator class :class:`VariableLengthResponseGeneratorPowerShell` supports | ||
the invocation of commands with variable length output in a ``PowerShell``. | ||
|
||
In short, parser generator classes encapsulate details of shell-syntax and | ||
operation implementation. That allows support of different shell syntax, and | ||
the efficient implementation of specific higher level operations, e.g. | ||
``download``. It also allows users to extend the functionality of | ||
:class:`ShellCommandExecutor` by providing their own response generator | ||
classes. | ||
|
||
The module :mod:`datalad_next.shell.response_generators` provides two generally | ||
applicable abstract response generator classes: | ||
|
||
- :class:`VariableLengthResponseGenerator` | ||
|
||
- :class:`FixedLengthResponseGenerator` | ||
|
||
The functionality of the former is described above. The latter can be used to | ||
execute a command that will result in output of known | ||
length, e.g. ``echo -n 012345``. It reads the specified number of bytes and a | ||
trailing return code. This is more performant than the variable length response | ||
generator (because it does not have to search for the end marker). In addition, | ||
it does not rely on the uniqueness of the end marker. It is most useful for | ||
operation like ``download``, where the length of the output can be known in | ||
advance. | ||
|
||
As mentioned above, the classes :class:`VariableLengthResponseGenerator` and | ||
:class:`FixedLengthResponseGenerator` are abstract. The module | ||
:mod:`datalad_next.shell.response_generators` provides the following concrete | ||
implementations for them: | ||
|
||
- :class:`VariableLengthResponseGeneratorPosix` | ||
|
||
- :class:`VariableLengthResponseGeneratorPowerShell` | ||
|
||
- :class:`FixedLengthResponseGeneratorPosix` | ||
|
||
- :class:`FixedLengthResponseGeneratorPowerShell` | ||
|
||
When :func:`shell` is executed it will use a | ||
:class:`VariableLengthResponseClass` to skip the login message of the shell. | ||
This is done by executing a *zero command* (a command that will possibly | ||
generate some output, and successfully return) in the shell. The zero command is | ||
provided by the concrete implementation of class | ||
:class:`VariableLengthResponseGenerator`. For example, the zero command for | ||
POSIX shells is ``test 0 -eq 0``, for PowerShell it is ``Write-Host hello``. | ||
|
||
Because there is no way for func:`shell` to determine the kind of shell it | ||
connects to, the user can provide an alternative response generator class, in | ||
the ``zero_command_rg_class``-parameter. Instance of that class | ||
will then be used to execute the zero command. Currently, the following two | ||
response generator classes are available: | ||
|
||
- :class:`VariableLengthResponseGeneratorPosix`: works with POSIX-compliant | ||
shells, e.g. ``sh`` or ``bash``. This is the default. | ||
- :class:`VariableLengthResponseGeneratorPowerShell`: works with PowerShell. | ||
|
||
Whenever a command is executed via :meth:`ShellCommandExecutor.__call__`, the | ||
class identified by ``zero_command_rg_class`` will be used by default to create | ||
the final command list and to parse the result. Users can override this on a | ||
per-call basis by providing a different response generator class in the | ||
``response_generator``-parameter of :meth:`ShellCommandExecutor.__call__`. | ||
|
||
.. currentmodule:: datalad_next.shell | ||
|
||
.. autosummary:: | ||
:toctree: generated | ||
:recursive: | ||
|
||
ShellCommandExecutor | ||
ShellCommandResponseGenerator | ||
VariableLengthResponseGenerator | ||
VariableLengthResponseGeneratorPosix | ||
VariableLengthResponseGeneratorPowerShell | ||
FixedLengthResponseGenerator | ||
FixedLengthResponseGeneratorPosix | ||
FixedLengthResponseGeneratorPowerShell | ||
DownloadResponseGenerator | ||
DownloadResponseGeneratorPosix | ||
operations.posix.upload | ||
operations.posix.download | ||
operations.posix.delete | ||
""" | ||
|
||
|
||
__all__ = [ | ||
'shell', | ||
'posix', | ||
] | ||
|
||
from .shell import ( | ||
shell, | ||
ShellCommandExecutor, | ||
) | ||
|
||
from .operations import posix | ||
from .operations.posix import ( | ||
DownloadResponseGenerator, | ||
DownloadResponseGeneratorPosix, | ||
) | ||
from .response_generators import ( | ||
FixedLengthResponseGenerator, | ||
FixedLengthResponseGeneratorPosix, | ||
FixedLengthResponseGeneratorPowerShell, | ||
ShellCommandResponseGenerator, | ||
VariableLengthResponseGenerator, | ||
VariableLengthResponseGeneratorPosix, | ||
VariableLengthResponseGeneratorPowerShell, | ||
) | ||
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Should there be a common naming/selection strategy?
Client code must choose the current implementation for there use case, but it seems a simple label for a shell-setup is not sufficient. We have a
posix
operations module, but no one for response generators.Why not have one
posix
module and it has all pieces needed?Along the same lines, I think we should also avoid that client code has to switch class names per use case. Now thye need to select a module to import from and what to import also. This means that client code also needs to maintain a mapping of the API. If we would provide modules with the exact same API, client code could be simplified to just get the label of the "platform/shell-type" module to work with.
I also want to point out that the promise is that everything imported from
datalad_next.shell
is stable (or needs a minor/major release to change). We import the entireposix
module, which not only has the pieces that aim to be a "public" API. I think this calls for a dedicated module that exposes the API.