[GH-ISSUE #300] [ integration ] expose python api #818

Closed
opened 2026-03-15 10:35:57 +03:00 by kerem · 4 comments
Owner

Originally created by @vsoch on GitHub (Apr 21, 2018).
Original GitHub issue: https://github.com/asciinema/asciinema/issues/300

This is probably a no brainer, but I'd like to be able to (programatically from within Python) have an easy way to control asciinema recording, so I can integrate it into my client's functions without needing to send commands to subprocess. I started to trace what was happening and come up with something (rather hacky):

    import asciinema.config as aconfig
    from asciinema.api import Api
    from asciinema.commands.record import RecordCommand

    cfg = aconfig.load()
    api = Api(cfg.api_url, os.environ.get("USER"), cfg.api_token)

    if not filename:
        filename = generate_temporary_file(prefix='asciinema')

    # Create dummy class to pass in as args

    class settings(object):
        filename = filename
        env = os.environ
        rec_stdin = False
        command = os.environ['SHELL']
        title = title or "Helpme Recording"
        assume_yes = False 
        quiet = False
        max_wait = 10

    args = settings()
    return_code = RecordCommand(api, args=args)

but stopped because I was worried about not completely mimicking the parser that is defined here. If we could have discussion about how this might be done, I have a few ideas!

Expose Parser
you could expose functions to just return the parser for each command, that way a function like mine could do something like:

parser = get_record_parser()
args = parser.parse_args(["arg1", "--val1","flag1"])

Python API?
As a developer, it would be really awesome to see documentation and examples for how to control from within Python. Given that we call a subshell, I think it should technically work to record from /bin/bash (or $SHELL) and then do it's thing and exit right back where it left off :)

Expose Generation Function
It would be more intuitive to expose a generation function that either prepares a dummy parser (akin to what I was trying to do) to pass on to RecordCommand, or a customization of RecordCommand so it takes more nicely named arguments, e.g.,

class RecordCommand

    def __init__(self, api, filename, title="HelpMe Recording", quiet=True):
        Command.__init__(self, quiet)
        self.api = api
        self.filename = filename
       ...

etc.! What I'm probably going to try is overriding the init function before I call it with something like this, and tweak until I have the minimum set of variables needed to get it working, haha.

And thank you so much for asciinema! It continues to be one of my favorite things, and I'm finally having some time to integrate it into some cool tools :)

Originally created by @vsoch on GitHub (Apr 21, 2018). Original GitHub issue: https://github.com/asciinema/asciinema/issues/300 This is probably a no brainer, but I'd like to be able to (programatically from within Python) have an easy way to control asciinema recording, so I can integrate it into my client's functions without needing to send commands to subprocess. I started to trace what was happening and come up with something (rather hacky): ```python import asciinema.config as aconfig from asciinema.api import Api from asciinema.commands.record import RecordCommand cfg = aconfig.load() api = Api(cfg.api_url, os.environ.get("USER"), cfg.api_token) if not filename: filename = generate_temporary_file(prefix='asciinema') # Create dummy class to pass in as args class settings(object): filename = filename env = os.environ rec_stdin = False command = os.environ['SHELL'] title = title or "Helpme Recording" assume_yes = False quiet = False max_wait = 10 args = settings() return_code = RecordCommand(api, args=args) ``` but stopped because I was worried about not completely mimicking the parser that is defined [here](https://github.com/asciinema/asciinema/blob/develop/asciinema/__main__.py#L91). If we could have discussion about how this might be done, I have a few ideas! *Expose Parser* you could expose functions to just return the parser for each command, that way a function like mine could do something like: ```python parser = get_record_parser() args = parser.parse_args(["arg1", "--val1","flag1"]) ``` *Python API?* As a developer, it would be really awesome to see documentation and examples for how to control from within Python. Given that we call a subshell, I think it should technically work to record from /bin/bash (or `$SHELL`) and then do it's thing and exit right back where it left off :) *Expose Generation Function* It would be more intuitive to expose a generation function that either prepares a dummy parser (akin to what I was trying to do) to pass on to RecordCommand, or a customization of RecordCommand so it takes more nicely named arguments, e.g., ```python class RecordCommand def __init__(self, api, filename, title="HelpMe Recording", quiet=True): Command.__init__(self, quiet) self.api = api self.filename = filename ... ``` etc.! What I'm probably going to try is overriding the __init__ function before I call it with something like this, and tweak until I have the minimum set of variables needed to get it working, haha. And thank you so much for asciinema! It continues to be one of my [favorite things](https://asciinema.org/~vs), and I'm finally having some time to integrate it into some cool tools :)
kerem closed this issue 2026-03-15 10:36:02 +03:00
Author
Owner

@vsoch commented on GitHub (Apr 21, 2018):

Here is a quick example of something:

from asciinema.commands.record import RecordCommand
from asciinema.commands.command import Command
import asciinema.asciicast.v2 as v2
import asciinema.asciicast.raw as raw
import tempfile

# Create subclass of RecordCommand to pass in arguments

class HelpMeRecord(RecordCommand):

    def __init__(self, api, 
                 filename=None, 
                 quiet=False, 
                 env=None,
                 env_whitelist='',
                 record_stdin=False,
                 command=None,
                 title="HelpMe Recording",
                 append=False,
                 overwrite=False,
                 record_raw=False):

        # If no custom file selected, create for user
        if filename is None:
            filename = self.generate_temporary_file()

        Command.__init__(self, quiet=quiet)
        self.api = api
        self.filename = filename
        self.rec_stdin = record_stdin
        self.command = command or os.environ['SHELL']
        self.env_whitelist = ''
        self.title = title
        self.assume_yes = quiet
        self.idle_time_limit = 10
        self.append = append
        self.overwrite = overwrite
        self.raw = record_raw
        self.recorder = raw.Recorder() if record_raw else v2.Recorder()
        self.env = env if env is not None else os.environ


    def generate_temporary_file(self, folder='/tmp', 
                                      prefix='helpme', 
                                      ext='json'):

        '''write a temporary file, in base directory with a particular extension.
      
           Parameters
           ==========
           folder: the base directory to write in. 
           prefix: the prefix to use
           ext: the extension to use.

        '''        
        tmp = next(tempfile._get_candidate_names())
        return '%s/%s.%s.%s' %(folder, prefix, tmp, ext)


def record_asciinema():
    '''a wrapper around generation of an asciinema.api.Api and a custom 
       recorder to pull out the input arguments to the Record from argparse.
       The function generates a filename in advance and a return code
       so we can check the final status. 
    '''

    import asciinema.config as aconfig
    from asciinema.api import Api

    # Load the API class

    cfg = aconfig.load()
    api = Api(cfg.api_url, os.environ.get("USER"), cfg.install_id)

    # Create dummy class to pass in as args
    recorder = HelpMeRecord(api)
    code = recorder.execute()
    
    if code == 0 and os.path.exists(recorder.filename):
        return recorder.filename
    print('Problem generating %s, return code %s' %(recorder.filename, code))

and then running (from within Python)

In [50]: record_asciinema()
asciinema: recording asciicast to /tmp/helpme.pjwrzqhg.json
asciinema: press <ctrl-d> or type "exit" when you're done
Now using node v8.10.0 (npm v5.6.0)
vanessa@vanessa-ThinkPad-T460s:~$ This is a test reccording!
This: command not found
vanessa@vanessa-ThinkPad-T460s:~$ exit
exit
asciinema: recording finished
asciinema: asciicast saved to /tmp/helpme.pjwrzqhg.json
Out[50]: '/tmp/helpme.pjwrzqhg.json'

and then the file looks ok, even despite the stupid things I typed :)

$  cat /tmp/helpme.pjwrzqhg.json
{"height": 24, "version": 2, "idle_time_limit": 10, "title": "HelpMe Recording", "timestamp": 1524341611, "width": 80}
[0.78804, "o", "Now using node v8.10.0 (npm v5.6.0)\r\n"]
[0.789836, "o", "\u001b]0;vanessa@vanessa-ThinkPad-T460s: ~\u0007\u001b[01;32mvanessa@vanessa-ThinkPad-T460s\u001b[00m:\u001b[01;34m~\u001b[00m$ "]
[2.443566, "o", "T"]
[2.578993, "o", "h"]
[2.654273, "o", "i"]
[2.736367, "o", "s"]
[2.89482, "o", " "]
[2.961478, "o", "i"]
[3.025505, "o", "s"]
[3.194627, "o", " "]
[3.660986, "o", "a"]
[3.793766, "o", " "]
[4.160656, "o", "t"]
[4.23192, "o", "e"]
[4.332691, "o", "s"]
[4.425724, "o", "t"]
[4.541115, "o", " "]
[4.650229, "o", "r"]
[4.693935, "o", "e"]
[4.882253, "o", "c"]
[4.945723, "o", "c"]
[5.01765, "o", "o"]
[5.125637, "o", "r"]
[5.333498, "o", "d"]
[5.608731, "o", "i"]
[5.675873, "o", "n"]
[5.745732, "o", "g"]
[6.049181, "o", "!"]
[6.414821, "o", "\r\n"]
[6.537888, "o", "This: command not found\r\n"]
[6.545381, "o", "\u001b]0;vanessa@vanessa-ThinkPad-T460s: ~\u0007\u001b[01;32mvanessa@vanessa-ThinkPad-T460s\u001b[00m:\u001b[01;34m~\u001b[00m$ "]
[7.024822, "o", "e"]
[7.770842, "o", "x"]
[7.870349, "o", "i"]
[7.981964, "o", "t"]
[8.209445, "o", "\r\n"]
[8.209833, "o", "exit\r\n"]

Are there other users integrated asciinema like this? It seems like something that would definitely be wanted! I'm happy to use this hack I came up with, but wanted to bring up something more robust so others might benefit having it.

<!-- gh-comment-id:383326741 --> @vsoch commented on GitHub (Apr 21, 2018): Here is a quick example of something: ```python from asciinema.commands.record import RecordCommand from asciinema.commands.command import Command import asciinema.asciicast.v2 as v2 import asciinema.asciicast.raw as raw import tempfile # Create subclass of RecordCommand to pass in arguments class HelpMeRecord(RecordCommand): def __init__(self, api, filename=None, quiet=False, env=None, env_whitelist='', record_stdin=False, command=None, title="HelpMe Recording", append=False, overwrite=False, record_raw=False): # If no custom file selected, create for user if filename is None: filename = self.generate_temporary_file() Command.__init__(self, quiet=quiet) self.api = api self.filename = filename self.rec_stdin = record_stdin self.command = command or os.environ['SHELL'] self.env_whitelist = '' self.title = title self.assume_yes = quiet self.idle_time_limit = 10 self.append = append self.overwrite = overwrite self.raw = record_raw self.recorder = raw.Recorder() if record_raw else v2.Recorder() self.env = env if env is not None else os.environ def generate_temporary_file(self, folder='/tmp', prefix='helpme', ext='json'): '''write a temporary file, in base directory with a particular extension. Parameters ========== folder: the base directory to write in. prefix: the prefix to use ext: the extension to use. ''' tmp = next(tempfile._get_candidate_names()) return '%s/%s.%s.%s' %(folder, prefix, tmp, ext) def record_asciinema(): '''a wrapper around generation of an asciinema.api.Api and a custom recorder to pull out the input arguments to the Record from argparse. The function generates a filename in advance and a return code so we can check the final status. ''' import asciinema.config as aconfig from asciinema.api import Api # Load the API class cfg = aconfig.load() api = Api(cfg.api_url, os.environ.get("USER"), cfg.install_id) # Create dummy class to pass in as args recorder = HelpMeRecord(api) code = recorder.execute() if code == 0 and os.path.exists(recorder.filename): return recorder.filename print('Problem generating %s, return code %s' %(recorder.filename, code)) ``` and then running (from within Python) ```python In [50]: record_asciinema() asciinema: recording asciicast to /tmp/helpme.pjwrzqhg.json asciinema: press <ctrl-d> or type "exit" when you're done Now using node v8.10.0 (npm v5.6.0) vanessa@vanessa-ThinkPad-T460s:~$ This is a test reccording! This: command not found vanessa@vanessa-ThinkPad-T460s:~$ exit exit asciinema: recording finished asciinema: asciicast saved to /tmp/helpme.pjwrzqhg.json Out[50]: '/tmp/helpme.pjwrzqhg.json' ``` and then the file looks ok, even despite the stupid things I typed :) ```bash $ cat /tmp/helpme.pjwrzqhg.json {"height": 24, "version": 2, "idle_time_limit": 10, "title": "HelpMe Recording", "timestamp": 1524341611, "width": 80} [0.78804, "o", "Now using node v8.10.0 (npm v5.6.0)\r\n"] [0.789836, "o", "\u001b]0;vanessa@vanessa-ThinkPad-T460s: ~\u0007\u001b[01;32mvanessa@vanessa-ThinkPad-T460s\u001b[00m:\u001b[01;34m~\u001b[00m$ "] [2.443566, "o", "T"] [2.578993, "o", "h"] [2.654273, "o", "i"] [2.736367, "o", "s"] [2.89482, "o", " "] [2.961478, "o", "i"] [3.025505, "o", "s"] [3.194627, "o", " "] [3.660986, "o", "a"] [3.793766, "o", " "] [4.160656, "o", "t"] [4.23192, "o", "e"] [4.332691, "o", "s"] [4.425724, "o", "t"] [4.541115, "o", " "] [4.650229, "o", "r"] [4.693935, "o", "e"] [4.882253, "o", "c"] [4.945723, "o", "c"] [5.01765, "o", "o"] [5.125637, "o", "r"] [5.333498, "o", "d"] [5.608731, "o", "i"] [5.675873, "o", "n"] [5.745732, "o", "g"] [6.049181, "o", "!"] [6.414821, "o", "\r\n"] [6.537888, "o", "This: command not found\r\n"] [6.545381, "o", "\u001b]0;vanessa@vanessa-ThinkPad-T460s: ~\u0007\u001b[01;32mvanessa@vanessa-ThinkPad-T460s\u001b[00m:\u001b[01;34m~\u001b[00m$ "] [7.024822, "o", "e"] [7.770842, "o", "x"] [7.870349, "o", "i"] [7.981964, "o", "t"] [8.209445, "o", "\r\n"] [8.209833, "o", "exit\r\n"] ``` Are there other users integrated asciinema like this? It seems like something that would definitely be wanted! I'm happy to use this hack I came up with, but wanted to bring up something more robust so others might benefit having it.
Author
Owner

@ku1ik commented on GitHub (Apr 24, 2018):

Hey! Looks like you've figured the hard parts :)

If I understand correctly you would like to initiate recording session from your own Python code, right?

I think it would be quite straightforward to expose this functionality in even nicer API for such purposes.

I've been refactoring the code recently to make it more user-friendly, see here: https://github.com/asciinema/discussions/issues/38 - this one is for reading and writing asciicast files (not useful for you purpose though).

<!-- gh-comment-id:383845086 --> @ku1ik commented on GitHub (Apr 24, 2018): Hey! Looks like you've figured the hard parts :) If I understand correctly you would like to initiate recording session from your own Python code, right? I think it would be quite straightforward to expose this functionality in even nicer API for such purposes. I've been refactoring the code recently to make it more user-friendly, see here: https://github.com/asciinema/discussions/issues/38 - this one is for reading and writing asciicast files (not useful for you purpose though).
Author
Owner

@vsoch commented on GitHub (Apr 25, 2018):

hey @sickill ! Yes this would be a really great feature to have. If you are interested, I got (most of it) working here, just for the commands that I needed (record and upload) for this library --> https://vsoch.github.io/helpme/helper-github specifically for record and submit. I'm really excited about using asciinema in this way - this client prompts the user for asking a question, records the terminal if they want to show an issue, and then submits to uservoice or Github. I can do other clients if anyone has ideas. Could you tell me more about the API you are developing for writing the files? What are example use cases of when we would want to do this?

<!-- gh-comment-id:384293254 --> @vsoch commented on GitHub (Apr 25, 2018): hey @sickill ! Yes this would be a really great feature to have. If you are interested, I got (most of it) working here, just for the commands that I needed (record and upload) for this library --> https://vsoch.github.io/helpme/helper-github specifically for [record](https://github.com/vsoch/helpme/blob/master/helpme/action/record.py#L80) and [submit](https://github.com/vsoch/helpme/blob/master/helpme/action/submit.py#L23). I'm really excited about using asciinema in this way - this client prompts the user for asking a question, records the terminal if they want to show an issue, and then submits to uservoice or Github. I can do other clients if anyone has ideas. Could you tell me more about the API you are developing for writing the files? What are example use cases of when we would want to do this?
Author
Owner

@ku1ik commented on GitHub (Jan 12, 2019):

Hey I just released 2.0.2 with this high level API for recording: https://github.com/asciinema/asciinema/blob/develop/asciinema/init.py#L19

It's used by RecordCommand: https://github.com/asciinema/asciinema/blob/develop/asciinema/commands/record.py#L69-L78

Most of the arguments are optional, they have reasonable defaults, the only required one is path for the resulting asciicast file.

<!-- gh-comment-id:453773909 --> @ku1ik commented on GitHub (Jan 12, 2019): Hey I just released 2.0.2 with this high level API for recording: https://github.com/asciinema/asciinema/blob/develop/asciinema/__init__.py#L19 It's used by RecordCommand: https://github.com/asciinema/asciinema/blob/develop/asciinema/commands/record.py#L69-L78 Most of the arguments are optional, they have reasonable defaults, the only required one is path for the resulting asciicast file.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/asciinema#818
No description provided.