def cli(ctx, apiroot, config, organization): """A command line tool for working with Transcriptic.""" if ctx.invoked_subcommand in [ 'login', 'compile', 'preview', 'summarize', 'init' ]: # For login/local commands, initialize empty connection ctx.obj = ContextObject() ctx.obj.api = Connection(use_environ=False) else: try: ctx.obj = ContextObject() ctx.obj.api = Connection.from_file(config) if organization is not None: ctx.obj.api.organization_id = organization if apiroot is not None: ctx.obj.api.api_root = apiroot except: click.echo( "Welcome to TxPy! It seems like your `.transcriptic` config file is missing or out of date" ) analytics = click.confirm( "Send TxPy CLI usage information to improve the CLI user " "experience?", default=True) ctx.obj.api = Connection( use_environ=False) # Initialize empty connection ctx.invoke(login, analytics=analytics) if ctx.obj.api.analytics: try: ctx.obj.api._post_analytics(event_action=ctx.invoked_subcommand, event_category="cli") except: pass
def format_commands(self, ctx, formatter): """Custom formatter to control whether a command is displayed Note: This is only called when formatting the help message. """ ctx.obj = ContextObject() try: ctx.obj.api = Connection.from_file('~/.transcriptic') except (FileNotFoundError, OSError): # This defaults to feature_groups = [] ctx.obj.api = Connection() rows = [] for subcommand in self.list_commands(ctx): cmd = self.get_command(ctx, subcommand) if cmd is None: continue try: if cmd.feature is not None and \ cmd.feature in ctx.obj.api.feature_groups: help = cmd.short_help or '' rows.append((subcommand, help)) else: continue except AttributeError: help = cmd.short_help or '' rows.append((subcommand, help)) if rows: with formatter.section('Commands'): formatter.write_dl(rows)
def cli(ctx, apiroot, config, organization): """A command line tool for working with Transcriptic.""" if ctx.invoked_subcommand in ['login', 'compile', 'preview', 'summarize', 'init']: # For login/local commands, initialize empty connection ctx.obj = ContextObject() ctx.obj.api = Connection(use_environ=False) else: try: ctx.obj = ContextObject() ctx.obj.api = Connection.from_file(config) if organization is not None: ctx.obj.api.organization_id = organization if apiroot is not None: ctx.obj.api.api_root = apiroot except: click.echo("Welcome to TxPy! It seems like your `.transcriptic` config file is missing or out of date") analytics = click.confirm("Send TxPy CLI usage information to improve the CLI user " "experience?", default=True) ctx.obj.api = Connection(use_environ=False) # Initialize empty connection ctx.invoke(login, analytics=analytics) if ctx.obj.api.analytics: try: ctx.obj.api._post_analytics(event_action=ctx.invoked_subcommand, event_category="cli") except: pass
def summarize(ctx, file, tree, lookup, runtime): """Summarize Autoprotocol as a list of plain English steps, as well as a visualized Job Tree contingent upon desired runtime allowance (in seconds). A Job Tree refers to a structure of protocol based on container dependency, where each node, and its corresponding number, represents an instruction of the protocol. More specifically, the tree structure contains process branches, in which the x-axis refers to the dependency depth in a given branch, while the y-axis refers to the traversal of branches themselves. Example usage is as follows: python my_script.py | transcriptic summarize --tree python my_script.py | transcriptic summarize --tree --runtime 20 """ with click.open_file(file, 'r') as f: try: protocol = json.loads(f.read()) except ValueError: click.echo( "The autoprotocol you're trying to summarize is invalid.") return if lookup: try: config = '~/.transcriptic' ctx.obj = ContextObject() ctx.obj.api = Connection.from_file(config) parser = AutoprotocolParser(protocol, ctx=ctx) except: click.echo( "Connection with Transcriptic failed. " "Summarizing without lookup.", err=True) parser = AutoprotocolParser(protocol) else: parser = AutoprotocolParser(protocol) if tree: import multiprocessing print("\nGenerating Job Tree...") p = multiprocessing.Process(target=parser.job_tree) p.start() # Wait for <runtime_allowance> seconds or until process finishes p.join(runtime) # If thread is still active if p.is_alive(): print("Still running... Aborting tree construction.") print( "Please allow for more runtime allowance, or opt for no tree construction.\n" ) # Terminate p.terminate() p.join() else: print("\nYour Job Tree is complete!\n")
def summarize_cmd(ctx, file, html, tree, lookup, runtime): """Summarize Autoprotocol as a list of plain English steps, as well as a visualized Job Tree contingent upon desired runtime allowance (in seconds). A Job Tree refers to a structure of protocol based on container dependency, where each node, and its corresponding number, represents an instruction of the protocol. More specifically, the tree structure contains process branches, in which the x-axis refers to the dependency depth in a given branch, while the y-axis refers to the traversal of branches themselves. Example usage is as follows: python my_script.py | transcriptic summarize --tree python my_script.py | transcriptic summarize --tree --runtime 20 python my_script.py | transcriptic summarize --html """ if lookup or html: try: config = '~/.transcriptic' ctx.obj = ContextObject() ctx.obj.api = Connection.from_file(config) except: click.echo("Connection with Transcriptic failed. " "Summarizing without lookup.", err=True) api = ctx.obj.api commands.summarize(api, file, html, tree, lookup, runtime)
def test_api(monkeypatch): from transcriptic.config import Connection from .helpers.mockAPI import _req_call as mockCall api = Connection(email="*****@*****.**", organization_id="mock", api_root="mock-api") monkeypatch.setattr(api, '_req_call', mockCall) return api
def connect(transcriptic_path="~/.transcriptic"): #TODO: Mirror login code from CLI try: api = Connection.from_file(transcriptic_path) except: print( "Unable to find .transcriptic file, please ensure the right path is provided" )
def summarize(ctx, file, tree, lookup, runtime): """Summarize Autoprotocol as a list of plain English steps, as well as a visualized Job Tree contingent upon desired runtime allowance (in seconds). A Job Tree refers to a structure of protocol based on container dependency, where each node, and its corresponding number, represents an instruction of the protocol. More specifically, the tree structure contains process branches, in which the x-axis refers to the dependency depth in a given branch, while the y-axis refers to the traversal of branches themselves. Example usage is as follows: python my_script.py | transcriptic summarize --tree python my_script.py | transcriptic summarize --tree --runtime 20 """ with click.open_file(file, 'r') as f: try: protocol = json.loads(f.read()) except ValueError: click.echo( "The autoprotocol you're trying to summarize is invalid.") return if lookup: try: config = '~/.transcriptic' ctx.obj = ContextObject() ctx.obj.api = Connection.from_file(config) parser = AutoprotocolParser(protocol, ctx=ctx) except: click.echo("Connection with Transcriptic failed. " "Summarizing without lookup.", err=True) parser = AutoprotocolParser(protocol) else: parser = AutoprotocolParser(protocol) if tree: import multiprocessing print("\nGenerating Job Tree...") p = multiprocessing.Process(target=parser.job_tree) p.start() # Wait for <runtime_allowance> seconds or until process finishes p.join(runtime) # If thread is still active if p.is_alive(): print("Still running... Aborting tree construction.") print( "Please allow for more runtime allowance, or opt for no tree construction.\n") # Terminate p.terminate() p.join() else: print("\nYour Job Tree is complete!\n")
def cli(ctx, apiroot, config, organization): '''A command line tool for working with Transcriptic.''' if ctx.invoked_subcommand not in ['login', 'preview', 'run']: try: ctx.obj = Connection.from_file(config) if organization is not None: ctx.obj.organization_id = organization if apiroot is not None: ctx.obj.api_root = apiroot except IOError: click.echo("Error reading config file, running " "`transcriptic login` ...") ctx.invoke(login)
def protocol_response(method, protocol_path=None, response_path=None, **kwargs): """ Helper function for getting protocol response and dumping json to response path Caveat Emptor: Does not do any additional checks on the response object, just dumps to json if possible """ from transcriptic import api if not api: from transcriptic.config import Connection api = Connection.from_file("~/.transcriptic") protocol = json.loads(open(protocol_path).read()) response = requests.post(api.get_route(method), headers=api.headers, data=json.dumps({'protocol': protocol})) with open(response_path, 'w') as out_file: json.dump(response.json(), out_file, indent=2)
def login(ctx, api_root): '''Authenticate to your Transcriptic account.''' email = click.prompt('Email') password = click.prompt('Password', hide_input=True) r = requests.post("%s/users/sign_in" % api_root, data=json.dumps({ 'user': { 'email': email, 'password': password, }, }), headers={ 'Accept': 'application/json', 'Content-Type': 'application/json', }) if r.status_code != 200: click.echo("Error logging into Transcriptic: %s" % r.json()['error']) sys.exit(1) user = r.json() token = (user.get('authentication_token') or user['test_mode_authentication_token']) if len(user['organizations']) < 1: click.echo( "Error: You don't appear to belong to any organizations. \nVisit %s " "and create an organization." % api_root) sys.exit(1) if len(user['organizations']) == 1: organization = user['organizations'][0]['subdomain'] else: click.echo("You belong to %s organizations:" % len(user['organizations'])) for o in user['organizations']: click.echo(" %s (%s)" % (o['name'], o['subdomain'])) organization = click.prompt( 'Which would you like to login as', default=user['organizations'][0]['subdomain'], prompt_suffix='? ') r = requests.get('%s/%s' % (api_root, organization), headers={ 'X-User-Email': email, 'X-User-Token': token, 'Accept': 'application/json', }) if r.status_code != 200: click.echo("Error accessing organization: %s" % r.text) sys.exit(1) ctx.obj = Connection(email, token, organization, api_root=api_root) ctx.obj.save(ctx.parent.params['config']) click.echo('Logged in as %s (%s)' % (user['email'], organization))
def protocol_response(method, protocol_path=None, response_path=None, **kwargs): """ Helper function for getting protocol response and dumping json to response path Caveat Emptor: Does not do any additional checks on the response object, just dumps to json if possible """ from transcriptic import api if not api: from transcriptic.config import Connection api = Connection.from_file("~/.transcriptic") protocol = json.loads(open(protocol_path).read()) response = requests.post(api.get_route(method), headers=api.session.headers, data=json.dumps({'protocol': protocol})) with open(response_path, 'w') as out_file: json.dump(response.json(), out_file, indent=2)
def connect(transcriptic_path="~/.transcriptic"): #TODO: Mirror login code from CLI try: api = Connection.from_file(transcriptic_path) except: print ("Unable to find .transcriptic file, please ensure the right path is provided")
def login(ctx, api_root, analytics=True): """Authenticate to your Transcriptic account.""" email = click.prompt('Email') password = click.prompt('Password', hide_input=True) r = ctx.obj.api.post(routes.login(api_root=api_root), data=json.dumps({ 'user': { 'email': email, 'password': password, }, }), headers={ 'Accept': 'application/json', 'Content-Type': 'application/json', }, status_response={ '200': lambda resp: resp, 'default': lambda resp: resp }, custom_request=False) if r.status_code != 200: click.echo("Error logging into Transcriptic: %s" % r.json()['error']) sys.exit(1) user = r.json() token = (user.get('authentication_token') or user['test_mode_authentication_token']) user_id = user.get("id") if len(user['organizations']) < 1: click.echo("Error: You don't appear to belong to any organizations. \n" "Visit %s and create an organization." % api_root) sys.exit(1) if len(user['organizations']) == 1: organization = user['organizations'][0]['subdomain'] else: click.echo("You belong to %s organizations:" % len(user['organizations'])) for indx, o in enumerate(user['organizations']): click.echo("%s. %s (%s)" % (indx + 1, o['name'], o['subdomain'])) def parse_valid_org(indx): from click.exceptions import BadParameter try: return user['organizations'][int(indx) - 1]['subdomain'] except: raise BadParameter("Please enter an integer between 1 and %s" % (len(user['organizations'])), ctx=ctx) organization = click.prompt( 'Which organization would you like to log in as', default=1, prompt_suffix='? ', type=int, value_proc=lambda x: parse_valid_org(x)) r = ctx.obj.api.get(routes.get_organization(api_root=api_root, org_id=organization), headers={ 'X-User-Email': email, 'X-User-Token': token, 'Accept': 'application/json', }, status_response={ '200': lambda resp: resp, 'default': lambda resp: resp }, custom_request=True) if r.status_code != 200: click.echo("Error accessing organization: %s" % r.text) sys.exit(1) ctx.obj.api = Connection(email=email, token=token, organization_id=organization, api_root=api_root, user_id=user_id, analytics=analytics) ctx.obj.api.save(ctx.parent.params['config']) click.echo('Logged in as %s (%s)' % (user['email'], organization))
def cli(ctx, api_root, email, token, organization, config): """A command line tool for working with Transcriptic. Note: This is the main entry point of the CLI. If specifying credentials, note that the order of preference is: --flag, environment then config file. Example: `transcriptic --organization "my_org" projects` >> `export USER_ORGANIZATION="my_org"` >> `"organization_id": "my_org" in ~/.transcriptic """ # Initialize ContextObject to be used for storing api object ctx.obj = ContextObject() if ctx.invoked_subcommand in ['compile', 'preview', 'summarize', 'init']: # For local commands, initialize empty connection ctx.obj.api = Connection() elif ctx.invoked_subcommand == 'login': # Load analytics option from existing dotfile if present, else prompt try: api = Connection.from_file(config) api.api_root = ( api_root or os.environ.get('BASE_URL', None) or api.api_root ) ctx.obj.api = api except (OSError, IOError): ctx.obj.api = Connection() # Echo a warning if other options are defined for login if organization or email or token: click.echo("Only the `--api-root` option is applicable for the " "`login` command. All other options are ignored.") else: try: api = Connection.from_file(config) api.api_root = ( api_root or os.environ.get('BASE_URL', None) or api.api_root ) api.organization_id = ( organization or os.environ.get('USER_ORGANIZATION', None) or api.organization_id ) api.email = ( email or os.environ.get('USER_EMAIL', None) or api.email ) api.token = ( token or os.environ.get('USER_TOKEN', None) or api.token ) ctx.obj.api = api except (OSError, IOError): click.echo("Welcome to TxPy! It seems like your `.transcriptic` " "config file is missing or out of date") analytics = click.confirm("Send TxPy CLI usage information to " "improve the CLI user " "experience?", default=True) ctx.obj.api = Connection() # Initialize empty connection ctx.invoke(login_cmd, api_root=api_root, analytics=analytics) if ctx.obj.api.analytics: try: ctx.obj.api._post_analytics(event_action=ctx.invoked_subcommand, event_category="cli") except requests.exceptions.RequestException: pass
def login(api, config, api_root=None, analytics=True): """Authenticate to your Transcriptic account.""" if api_root is None: # Always default to the pre-defined api-root if possible, else use # the secure.transcriptic.com domain try: api_root = api.api_root except ValueError: api_root = "https://secure.transcriptic.com" email = click.prompt('Email') password = click.prompt('Password', hide_input=True) try: r = api.post( routes.login(api_root=api_root), data=json.dumps({ 'user': { 'email': email, 'password': password, }, }), headers={ 'Accept': 'application/json', 'Content-Type': 'application/json', }, status_response={ '200': lambda resp: resp, '401': lambda resp: resp, 'default': lambda resp: resp } ) except requests.exceptions.RequestException: click.echo("Error logging into specified host: {}. Please check your " "internet connection and host name".format(api_root)) sys.exit(1) if r.status_code != 200: click.echo("Error logging into Transcriptic: %s" % r.json()['error']) sys.exit(1) user = r.json() token = (user.get('authentication_token') or user['test_mode_authentication_token']) user_id = user.get("id") feature_groups = user.get('feature_groups') organization = org_prompt(user['organizations']) r = api.get( routes.get_organization(api_root=api_root, org_id=organization), headers={ 'X-User-Email': email, 'X-User-Token': token, 'Accept': 'application/json'}, status_response={ '200': lambda resp: resp, 'default': lambda resp: resp} ) if r.status_code != 200: click.echo("Error accessing organization: %s" % r.text) sys.exit(1) api = Connection(email=email, token=token, organization_id=organization, api_root=api_root, user_id=user_id, analytics=analytics, feature_groups=feature_groups) api.save(config) click.echo('Logged in as %s (%s)' % (user['email'], organization))
def _req_call(method, route, **kwargs): if 'verify' not in kwargs: kwargs['verify'] = False return Connection._req_call(method, route, **kwargs)