Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Slightly simplified subprocesses

License

NotificationsYou must be signed in to change notification settings

pomponchik/suby

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

97 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

logo

DownloadsDownloadscodecovLines of codeHits-of-CodeTest-PackagePython versionsPyPI versionChecked with mypyRuff

Here is a small wrapper around thesubprocesses. You can find many similar wrappers, but this particular one differs from the others in the following parameters:

  • Beautiful minimalistic call syntax.
  • Ability to specify your callbacks to catchstdout andstderr.
  • Support forcancellation tokens.
  • You can set timeouts for subprocesses.
  • Logging of command execution.

Table of contents

Quick start

Install it:

pip install suby

And use:

importsubysuby('python','-c','print("hello, world!")')# > hello, world!

Run subprocess and look at the result

Thesuby module is a callable object and can be imported like this:

importsuby

If you use static type checking and get an error that it is impossible to call the module, use a more detailed import form - functionally, these two import ways are identical:

fromsubyimportsuby

Let's try to callsuby. You can use strings orpathlib.Path objects as positional arguments, but now we call it with only simple strings:

result=suby('python','-c','print("hello, world!")')print(result)# > SubprocessResult(id='e9f2d29acb4011ee8957320319d7541c', stdout='hello, world!\n', stderr='', returncode=0, killed_by_token=False)

We can see that it returns an object of theSubprocessResult class. It contains the following required fields:

  • id - a unique string that allows you to distinguish one result of calling the same command from another.
  • stdout - a string containing the entire buffered output of the command being run.
  • stderr - a string containing the entire buffered stderr of the command being run.
  • returncode - an integer indicating the return code of the subprocess.0 means that the process was completed successfully, the other options usually indicate something bad.
  • killed_by_token - a boolean flag indicating whether the subprocess was killed due totoken cancellation.

Output

By default, thestdout andstderr of the subprocess are forwarded to thestdout andstderr of the current process. The reading from the subprocess is continuous, and the output is every time a full line is read. For continuous reading fromstderr, a separate thread is created in the main process, so thatstdout andstderr are read independently.

You can override the output functions forstdout andstderr. To do this, you need to pass as argumentsstdout_callback andstderr_callback, respectively, some functions that accept a string as an argument. For example, you can color the output (the code example uses thetermcolor library):

importsubyfromtermcolorimportcoloreddefmy_new_stdout(string:str)->None:print(colored(string,'red'),end='')suby('python','-c','print("hello, world!")',stdout_callback=my_new_stdout)# > hello, world!# You can't see it here, but believe me, if you repeat the code at home, the output in the console will be red!

You can also completely disable the output by passingTrue as thecatch_output parameter:

suby('python','-c','print("hello, world!")',catch_output=True)# There's nothing here.

If you specifycatch_output=True, and at the same time redefine your functions for output, your functions will not be called either. In addition,suby always returnsthe result of executing the command, containing the full output. Thecatch_output argument can stop exactly the output, but it does not prevent the collection and buffering of the output.

Logging

By default,suby does not log command execution. However, you can pass a logger object to the function, and in this case logs will be recorded at the start of the command execution and at the end of the execution:

importloggingimportsubylogging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s",handlers=[logging.StreamHandler(),    ])suby('python','-c','pass',logger=logging.getLogger('logger_name'))# > 2024-02-22 02:15:08,155 [INFO] The beginning of the execution of the command "python -c pass".# > 2024-02-22 02:15:08,190 [INFO] The command "python -c pass" has been successfully executed.

The message about the start of the command execution is always done with theINFOlevel. If the command is completed successfully, the end message will also be with theINFO level. And if not -ERROR:

suby('python','-c','raise ValueError',logger=logging.getLogger('logger_name'),catch_exceptions=True,catch_output=True)# > 2024-02-22 02:20:25,549 [INFO] The beginning of the execution of the command "python -c "raise ValueError"".# > 2024-02-22 02:20:25,590 [ERROR] Error when executing the command "python -c "raise ValueError"".

If you don't need these details, just don't pass the logger object.

If you still prefer logging, you can use any object that implements thelogger protocol from theemptylog library, including ones from third-party libraries.

Exceptions

By default,suby raises exceptions in three cases:

  1. If the command you are calling ended with a return code not equal to0. In this case, you will see an exceptionsuby.RunningCommandError:
importsubytry:suby('python','-c','1/0')exceptsuby.RunningCommandErrorase:print(e)# > Error when executing the command "python -c 1/0".
  1. If you passed acancellation token when calling the command, and the token was canceled, an exception will be raisedcorresponding to the type of canceled token.This part of the functionality is integrated with thecantok library, so we recommend that you familiarize yourself with it beforehand.

  2. You have set atimeout for the operation and it has expired.

You can preventsuby from raising any exceptions. To do this, set thecatch_exceptions parameter toTrue:

result=suby('python','-c','import time; time.sleep(10_000)',timeout=1,catch_exceptions=True)print(result)# > SubprocessResult(id='c9125b90d03111ee9660320319d7541c', stdout='', stderr='', returncode=-9, killed_by_token=True)

Keep in mind that the full result of the subprocess call can also be found through theresult attribute of any raised exception:

try:suby('python','-c','import time; time.sleep(10_000)',timeout=1)exceptsuby.TimeoutCancellationErrorase:print(e.result)# > SubprocessResult(id='a80dc26cd03211eea347320319d7541c', stdout='', stderr='', returncode=-9, killed_by_token=True)

Working with Cancellation Tokens

suby is fully compatible with thecancellation token pattern and supports any token objects from thecantok library.

The essence of the pattern is that you can pass an object tosuby, from which it can find out whether the operation still needs to be continued or not. If not, it kills the subprocess. This pattern can be especially useful in the case of commands that are executed for a long time or for an unpredictably long time. When the result becomes unnecessary, there is no point in sitting and waiting for the command to work out.

So, you can pass your cancellation tokens tosuby. By default, canceling a token causes an exception to be raised:

fromrandomimportrandintimportsubyfromcantokimportConditionTokentoken=ConditionToken(lambda:randint(1,1000)==7)# This token will be cancelled when a random unlikely event occurs.suby('python','-c','import time; time.sleep(10_000)',token=token)# > cantok.errors.ConditionCancellationError: The cancellation condition was satisfied.

However, if you pass thecatch_exceptions=True argument, the exceptionwill not be raised. Instead, you will get theusual result of callingsuby with thekilled_by_token=True flag:

token=ConditionToken(lambda:randint(1,1000)==7)print(suby('python','-c','import time; time.sleep(10_000)',token=token,catch_exceptions=True))# > SubprocessResult(id='e92ccd54d24b11ee8376320319d7541c', stdout='', stderr='', returncode=-9, killed_by_token=True)

"Under the hood" a separate thread is created to track the status of the token. When the token is canceled, the thread kills the subprocess.

Timeouts

You can set a timeout forsuby. It must be an integer greater than zero, which indicates the number of seconds that the subprocess can continue to run. If the timeout expires before the subprocess completes, an exception will be raised:

importsubysuby('python','-c','import time; time.sleep(10_000)',timeout=1)# > cantok.errors.TimeoutCancellationError: The timeout of 1 seconds has expired.

To count the timeout, "under the hood"suby usesTimeoutToken from thecantok library.

The exception corresponding to this token was be reimported tosuby:

try:suby('python','-c','import time; time.sleep(10_000)',timeout=1)exceptsuby.TimeoutCancellationErrorase:# As you can see, TimeoutCancellationError is available in the suby module.print(e)# > The timeout of 1 seconds has expired.

Just as withregular cancellation tokens, you can prevent exceptions from being raised using thecatch_exceptions=True argument:

print(suby('python','-c','import time; time.sleep(10_000)',timeout=1,catch_exceptions=True))# > SubprocessResult(id='ea88c518d25011eeb25e320319d7541c', stdout='', stderr='', returncode=-9, killed_by_token=True)

[8]ページ先頭

©2009-2025 Movatter.jp