def fetch_recent(response: Response) -> str: """ :return: """ recent_paths = environ.configs.fetch('recent_paths', []) if len(recent_paths) < 1: response.fail( code='NO_RECENT_PROJECTS', message='There are no recent projects available' ).console() return None index, path = query.choice( 'Recently Opened Projects', 'Choose a project', recent_paths + ['Cancel'], 0 ) if index == len(recent_paths): return None return path
def check_status(response: Response, project: Project, step: ProjectStep, force: bool = False) -> str: """ :param response: :param project: :param step: :param force: :return: """ path = step.source_path if step.is_muted: environ.log('[{}]: Muted (skipped)'.format(step.definition.name)) return SKIP_STATUS if not os.path.exists(path): response.fail(code='MISSING_SOURCE_FILE', message='Source file not found "{}"'.format(path), id=step.definition.name, path=path).console('[{id}]: Not found "{path}"'.format( id=step.definition.name, path=path)) return ERROR_STATUS if not force and not step.is_dirty(): environ.log('[{}]: Nothing to update'.format(step.definition.name)) return SKIP_STATUS return OK_STATUS
def synchronize_step_names( project: 'projects.Project', insert_index: int = None ) -> Response: """ :param project: :param insert_index: """ response = Response() response.returned = dict() if not project.naming_scheme: return response create_mapper_func = functools.partial( create_rename_entry, insertion_index=insert_index ) step_renames = list([create_mapper_func(s) for s in project.steps]) step_renames = list(filter(lambda sr: (sr is not None), step_renames)) if not step_renames: return response try: backup_path = create_backup(project) except Exception as err: return response.fail( code='RENAME_BACKUP_ERROR', message='Unable to create backup name', error=err ).response try: step_renames = list([stash_source(sr) for sr in step_renames]) step_renames = list([unstash_source(sr) for sr in step_renames]) except Exception as err: return response.fail( code='RENAME_FILE_ERROR', message='Unable to rename files', error=err ).response response.returned = update_steps(project, step_renames) project.save() try: os.remove(backup_path) except PermissionError: pass return response
def fetch_last(response: Response) -> typing.Union[str, None]: """ Returns the last opened project path if such a path exists """ recent_paths = environ.configs.fetch('recent_paths', []) if len(recent_paths) < 1: response.fail( code='NO_RECENT_PROJECTS', message='No projects have been opened recently').console() return None return recent_paths[0]
def fetch_last(response: Response) -> typing.Union[str, None]: """ Returns the last opened project path if such a path exists """ recent_paths = environ.configs.fetch('recent_paths', []) if len(recent_paths) < 1: response.fail( code='NO_RECENT_PROJECTS', message='No projects have been opened recently' ).console() return None return recent_paths[0]
def get_parser( target_module, raw_args: typing.List[str], assigned_args: dict ) -> typing.Tuple[ArgumentParser, Response]: """ :param target_module: :param raw_args: :param assigned_args: :return: """ response = Response() description = None if hasattr(target_module, 'DESCRIPTION'): description = getattr(target_module, 'DESCRIPTION') parser = ArgumentParser( prog=target_module.NAME, add_help=False, description=description ) parser.add_argument( '-h', '--help', dest='show_help', action='store_true', default=False, help=cli.reformat( """ Print this help information instead of running the command """ ) ) if not hasattr(target_module, 'populate'): return parser, response try: getattr(target_module, 'populate')(parser, raw_args, assigned_args) except Exception as err: response.fail( code='ARGS_PARSE_ERROR', message='Unable to parse command arguments', error=err, name=target_module.NAME ).console(whitespace=1) return parser, response
def toggle_muting(response: Response, project: Project, step_name: str, value: bool = None) -> Response: """ :param response: :param project: :param step_name: :param value: :return: """ index = project.index_of_step(step_name) if index is None: return response.fail( code='NO_SUCH_STEP', message='No step found with name: "{}"'.format(step_name)).kernel( name=step_name).console().response step = project.steps[index] if value is None: value = not bool(step.is_muted) step.is_muted = value return response.notify( kind='SUCCESS', code='STEP_MUTE_ENABLED' if step.is_muted else 'STEP_MUTE_DISABLED', message='Muting has been {}'.format( 'enabled' if step.is_muted else 'disabled')).kernel( project=project.kernel_serialize()).console().response
def check_connection(url: str, force: bool) -> Response: """ """ response = Response() if force: return response ping = '{}/ping'.format(url) response.notify( kind='STARTING', code='CONNECTING', message='Establishing connection to: {}'.format(url) ).console( whitespace_top=1 ) try: result = requests.get(ping) if result.status_code != 200: raise request_exceptions.ConnectionError() except request_exceptions.InvalidURL as error: return response.fail( code='INVALID_URL', message='Invalid connection URL. Unable to establish connection', error=error ).console( whitespace=1 ).response except request_exceptions.ConnectionError as error: return response.fail( code='CONNECTION_ERROR', message='Unable to connect to remote cauldron host', error=error ).console( whitespace=1 ).response except Exception as error: return response.fail( code='CONNECT_COMMAND_ERROR', message='Failed to connect to the remote cauldron host', error=error ).console( whitespace=1 ).response
def synchronize_step_names(project: 'projects.Project', insert_index: int = None) -> Response: """ :param project: :param insert_index: """ response = Response() response.returned = dict() if not project.naming_scheme: return response create_mapper_func = functools.partial(create_rename_entry, insertion_index=insert_index) step_renames = list([create_mapper_func(s) for s in project.steps]) step_renames = list(filter(lambda sr: (sr is not None), step_renames)) if not step_renames: return response try: backup_path = create_backup(project) except Exception as err: return response.fail(code='RENAME_BACKUP_ERROR', message='Unable to create backup name', error=err).response try: step_renames = list([stash_source(sr) for sr in step_renames]) step_renames = list([unstash_source(sr) for sr in step_renames]) except Exception as err: return response.fail(code='RENAME_FILE_ERROR', message='Unable to rename files', error=err).response response.returned = update_steps(project, step_renames) project.save() try: os.remove(backup_path) except PermissionError: pass return response
def get_project(response: Response): """ :return: """ project = cauldron.project.internal_project if not project: response.fail(code='NO_OPEN_PROJECT', message='No project opened').console(""" [ERROR]: No project has been opened. Use the "open" command to open a project, or the "create" command to create a new one. """, whitespace=1) return None return project
def fetch_recent(response: Response) -> str: """ :return: """ recent_paths = environ.configs.fetch('recent_paths', []) if len(recent_paths) < 1: response.fail( code='NO_RECENT_PROJECTS', message='There are no recent projects available').console() return None index, path = query.choice('Recently Opened Projects', 'Choose a project', recent_paths + ['Cancel'], 0) if index == len(recent_paths): return None return path
def get_parser(module, raw_args: typing.List[str], assigned_args: dict) -> typing.Tuple[ArgumentParser, Response]: """ :param module: :param raw_args: :param assigned_args: :return: """ response = Response() description = None if hasattr(module, 'DESCRIPTION'): description = getattr(module, 'DESCRIPTION') parser = ArgumentParser(prog=module.NAME, add_help=False, description=description) parser.add_argument('-h', '--help', dest='show_help', action='store_true', default=False, help=cli.reformat(""" Print this help information instead of running the command """)) if not hasattr(module, 'populate'): return parser, response try: getattr(module, 'populate')(parser, raw_args, assigned_args) except Exception as err: response.fail(code='ARGS_PARSE_ERROR', message='Unable to parse command arguments', error=err, name=module.NAME).console(whitespace=1) return parser, response
def check_status( response: Response, project: Project, step: ProjectStep, force: bool = False ) -> str: """ :param response: :param project: :param step: :param force: :return: """ path = step.source_path if step.is_muted: environ.log('[{}]: Muted (skipped)'.format(step.definition.name)) return SKIP_STATUS if not os.path.exists(path): response.fail( code='MISSING_SOURCE_FILE', message='Source file not found "{}"'.format(path), id=step.definition.name, path=path ).console( '[{id}]: Not found "{path}"'.format( id=step.definition.name, path=path ) ) return ERROR_STATUS if not force and not step.is_dirty(): environ.log('[{}]: Nothing to update'.format(step.definition.name)) return SKIP_STATUS return OK_STATUS
def get_project(response: Response): """ :return: """ project = cauldron.project.internal_project if not project: response.fail( code='NO_OPEN_PROJECT', message='No project opened' ).console( """ [ERROR]: No project has been opened. Use the "open" command to open a project, or the "create" command to create a new one. """, whitespace=1 ) return None return project
def remove_step(response: Response, project: 'projects.Project', name: str, keep_file: bool = False) -> Response: """ :param response: :param project: :param name: :param keep_file: :return: """ step = project.remove_step(name) if not step: return response.fail( code='NO_SUCH_STEP', message='Step "{}" not found. Unable to remove.'.format( name)).kernel(name=name).console(whitespace=1).response project.save() project.write() if not keep_file: os.remove(step.source_path) res = step_support.synchronize_step_names(project) response.consume(res) if response.failed: return response step_renames = res.returned removed_name = 'REMOVED--{}'.format(uuid.uuid4()) step_renames[name] = dict(name=removed_name, title='') step_changes = [ dict(name=removed_name, filename=step.filename, action='removed') ] return response.update( project=project.kernel_serialize(), step_changes=step_changes, step_renames=step_renames).notify( kind='SUCCESS', code='STEP_REMOVED', message='Removed "{}" step from project'.format(name)).console( whitespace=1).response
def toggle_muting( response: Response, project: Project, step_name: str, value: bool = None ) -> Response: """ :param response: :param project: :param step_name: :param value: :return: """ index = project.index_of_step(step_name) if index is None: return response.fail( code='NO_SUCH_STEP', message='No step found with name: "{}"'.format(step_name) ).kernel( name=step_name ).console().response step = project.steps[index] if value is None: value = not bool(step.is_muted) step.is_muted = value return response.notify( kind='SUCCESS', code='STEP_MUTE_ENABLED' if step.is_muted else 'STEP_MUTE_DISABLED', message='Muting has been {}'.format( 'enabled' if step.is_muted else 'disabled' ) ).kernel( project=project.kernel_serialize() ).console().response
def modify_step(response: Response, project: Project, name: str, new_name: str = None, position: typing.Union[str, int] = None, title: str = None): """ :param response: :param project: :param name: :param new_name: :param position: :param title: :return: """ new_name = new_name if new_name else name old_index = project.index_of_step(name) new_index = index_from_location(response, project, position, old_index) if new_index > old_index: # If the current position of the step occurs before the new position # of the step, the new index has to be shifted by one to account for # the fact that this step will no longer be in this position when it # get placed in the position within the project new_index -= 1 old_step = project.remove_step(name) if not old_step: response.fail( code='NO_SUCH_STEP', message='Unable to modify unknown step "{}"'.format(name)).console( whitespace=1) return False source_path = old_step.source_path if os.path.exists(source_path): temp_path = '{}.cauldron_moving'.format(source_path) shutil.move(source_path, temp_path) else: temp_path = None res = step_support.synchronize_step_names(project, new_index) response.consume(res) step_renames = res.returned new_name_parts = naming.explode_filename(new_name, project.naming_scheme) new_name_parts['index'] = new_index if not project.naming_scheme and not new_name_parts['name']: new_name_parts['name'] = naming.find_default_filename( [s.definition.name for s in project.steps]) new_name = naming.assemble_filename(scheme=project.naming_scheme, **new_name_parts) step_data = {'name': new_name} if title is None: if old_step.definition.get('title'): step_data['title'] = old_step.definition.title else: step_data['title'] = title.strip('"') new_step = project.add_step(step_data, new_index) project.save() if not os.path.exists(new_step.source_path): if temp_path and os.path.exists(temp_path): shutil.move(temp_path, new_step.source_path) else: with open(new_step.source_path, 'w+') as f: f.write('') if new_index > 0: before_step = project.steps[new_index - 1].definition.name else: before_step = None step_renames[old_step.definition.name] = { 'name': new_step.definition.name, 'title': new_step.definition.title } step_changes = [ dict(name=new_step.definition.name, filename=new_step.filename, action='modified', after=before_step) ] response.update( project=project.kernel_serialize(), step_name=new_step.definition.name, step_changes=step_changes, step_renames=step_renames).notify( kind='SUCCESS', code='STEP_MODIFIED', message='Step modifications complete').console(whitespace=1) project.write() return True
def open_project( path: str, forget: bool = False, results_path: str = None ) -> Response: """ :param path: :param forget: :param results_path: :return: """ response = Response() try: # Try to close any open projects before opening a new one. runner.close() except Exception: # pragma: no cover pass path = environ.paths.clean(path) if not project_exists(response, path): return response.fail( code='PROJECT_NOT_FOUND', message='No project found at: "{}"'.format(path) ).console(whitespace=1).response if not load_project(response, path): return response.fail( code='PROJECT_NOT_LOADED', message='Unable to load project data' ).console(whitespace=1).response if not forget and not update_recent_paths(response, path): return response.fail( code='PROJECT_STATUS_FAILURE', message='Unable to update loaded project status' ).console(whitespace=1).response project = cauldron.project.get_internal_project() if results_path: project.results_path = results_path # Set the top-level display and cache values to the current project values # before running the step for availability within the step scripts cauldron.shared = cauldron.project.shared if not initialize_results(response, project): return response.fail( code='PROJECT_INIT_FAILURE', message='Unable to initialize loaded project' ).console(whitespace=1).response if not write_results(response, project): return response.fail( code='PROJECT_WRITE_FAILURE', message='Unable to write project notebook data' ).console(whitespace=1).response # Should no longer be needed because the source directory is included # in the library directories as of v0.4.7 # runner.add_library_path(project.source_directory) runner.reload_libraries(project.library_directories) return response.update( project=project.kernel_serialize() ).notify( kind='SUCCESS', code='PROJECT_OPENED', message='Opened project: {}'.format(path) ).console_header( project.title, level=2 ).console( """ PATH: {path} URL: {url} """.format(path=path, url=project.baked_url), whitespace=1 ).response
def modify_step( response: Response, project: Project, name: str, new_name: str = None, position: typing.Union[str, int] = None, title: str = None ): """ :param response: :param project: :param name: :param new_name: :param position: :param title: :return: """ new_name = new_name if new_name else name old_index = project.index_of_step(name) new_index = index_from_location(response, project, position, old_index) if new_index > old_index: # If the current position of the step occurs before the new position # of the step, the new index has to be shifted by one to account for # the fact that this step will no longer be in this position when it # get placed in the position within the project new_index -= 1 old_step = project.remove_step(name) if not old_step: response.fail( code='NO_SUCH_STEP', message='Unable to modify unknown step "{}"'.format(name) ).console( whitespace=1 ) return False source_path = old_step.source_path if os.path.exists(source_path): temp_path = '{}.cauldron_moving'.format(source_path) shutil.move(source_path, temp_path) else: temp_path = None res = step_support.synchronize_step_names(project, new_index) response.consume(res) step_renames = res.returned new_name_parts = naming.explode_filename(new_name, project.naming_scheme) new_name_parts['index'] = new_index if not project.naming_scheme and not new_name_parts['name']: new_name_parts['name'] = naming.find_default_filename( [s.definition.name for s in project.steps] ) new_name = naming.assemble_filename( scheme=project.naming_scheme, **new_name_parts ) step_data = {'name': new_name} if title is None: if old_step.definition.title: step_data['title'] = old_step.definition.title else: step_data['title'] = title.strip('"') new_step = project.add_step(step_data, new_index) project.save() if not os.path.exists(new_step.source_path): if temp_path and os.path.exists(temp_path): shutil.move(temp_path, new_step.source_path) else: with open(new_step.source_path, 'w+') as f: f.write('') if new_index > 0: before_step = project.steps[new_index - 1].definition.name else: before_step = None step_renames[old_step.definition.name] = { 'name': new_step.definition.name, 'title': new_step.definition.title } step_changes = [dict( name=new_step.definition.name, filename=new_step.filename, action='modified', after=before_step )] response.update( project=project.kernel_serialize(), step_name=new_step.definition.name, step_changes=step_changes, step_renames=step_renames ).notify( kind='SUCCESS', code='STEP_MODIFIED', message='Step modifications complete' ).console( whitespace=1 ) project.write() return True
def run_step( response: Response, project: Project, step: typing.Union[ProjectStep, str], force: bool = False ) -> bool: """ :param response: :param project: :param step: :param force: :return: """ step = get_step(project, step) if step is None: return False status = check_status(response, project, step, force) if status == ERROR_STATUS: return False step.error = None if status == SKIP_STATUS: return True os.chdir(os.path.dirname(step.source_path)) project.current_step = step step.report.clear() step.dom = None step.is_visible = True step.is_running = True step.progress_message = None step.progress = 0 step.sub_progress_message = None step.sub_progress = 0 step.start_time = datetime.utcnow() step.end_time = None # Set the top-level display and cache values to the current project values # before running the step for availability within the step scripts cauldron.shared = cauldron.project.shared redirection.enable(step) try: result = _execute_step(project, step) except Exception as error: result = dict( success=False, message='{}'.format(error), html_message='<pre>{}</pre>'.format(error) ) step.end_time = datetime.utcnow() os.chdir(os.path.expanduser('~')) step.mark_dirty(not result['success']) step.error = result.get('html_message') step.last_modified = time.time() if result['success'] else 0.0 step.is_running = False step.progress = 0 step.progress_message = None step.dumps() # Make sure this is called prior to printing response information to the # console or that will come along for the ride redirection.disable(step) step.project.stop_condition = result.get( 'stop_condition', StopCondition(False, False) ) if result['success']: environ.log('[{}]: Updated in {}'.format( step.definition.name, step.get_elapsed_timestamp() )) else: response.fail( message='Step execution error', code='EXECUTION_ERROR', project=project.kernel_serialize(), step_name=step.definition.name ).console_raw(result['message']) return result['success']
def remove_step( response: Response, project: 'projects.Project', name: str, keep_file: bool = False ) -> Response: """ :param response: :param project: :param name: :param keep_file: :return: """ step = project.remove_step(name) if not step: return response.fail( code='NO_SUCH_STEP', message='Step "{}" not found. Unable to remove.'.format(name) ).kernel( name=name ).console( whitespace=1 ).response project.save() project.write() if not keep_file: os.remove(step.source_path) res = step_support.synchronize_step_names(project) response.consume(res) if response.failed: return response step_renames = res.returned removed_name = 'REMOVED--{}'.format(uuid.uuid4()) step_renames[name] = dict( name=removed_name, title='' ) step_changes = [dict( name=removed_name, filename=step.filename, action='removed' )] return response.update( project=project.kernel_serialize(), step_changes=step_changes, step_renames=step_renames ).notify( kind='SUCCESS', code='STEP_REMOVED', message='Removed "{}" step from project'.format(name) ).console( whitespace=1 ).response
def run_step( response: Response, project: Project, step: typing.Union[ProjectStep, str], force: bool = False ) -> bool: """ :param response: :param project: :param step: :param force: :return: """ step = get_step(project, step) if step is None: return False status = check_status(response, project, step, force) if status == ERROR_STATUS: return False step.error = None if status == SKIP_STATUS: return True os.chdir(os.path.dirname(step.source_path)) project.current_step = step step.report.clear() step.dom = None step.is_running = True step.progress_message = None step.progress = 0 # Set the top-level display and cache values to the current project values # before running the step for availability within the step scripts cauldron.shared = cauldron.project.shared redirection.enable(step) try: result = _execute_step(project, step) except Exception as error: result = dict( success=False, message='{}'.format(error), html_message='<pre>{}</pre>'.format(error) ) os.chdir(os.path.expanduser('~')) step.mark_dirty(not result['success']) step.error = result.get('html_message') step.last_modified = time.time() if result['success'] else 0.0 step.is_running = False step.progress = 0 step.progress_message = None step.dumps() # Make sure this is called prior to printing response information to the # console or that will come along for the ride redirection.disable(step) if result['success']: environ.log('[{}]: Updated'.format(step.definition.name)) else: response.fail( message='Step execution error', code='EXECUTION_ERROR', project=project.kernel_serialize(), step_name=step.definition.name ).console_raw(result['message']) return result['success']
def open_project( path: str, forget: bool = False, results_path: str = None ) -> Response: """ :param path: :param forget: :param results_path: :return: """ response = Response() try: runner.close() except Exception: pass path = environ.paths.clean(path) if not project_exists(response, path): return response.fail( code='PROJECT_NOT_FOUND', message='No project found at: "{}"'.format(path) ).console(whitespace=1).response if not load_project(response, path): return response.fail( code='PROJECT_NOT_LOADED', message='Unable to load project data' ).console(whitespace=1).response if not forget and not update_recent_paths(response, path): return response.fail( code='PROJECT_STATUS_FAILURE', message='Unable to update loaded project status' ).console(whitespace=1).response project = cauldron.project.internal_project if results_path: project.results_path = results_path # Set the top-level display and cache values to the current project values # before running the step for availability within the step scripts cauldron.shared = cauldron.project.shared if not initialize_results(response, project): return response.fail( code='PROJECT_INIT_FAILURE', message='Unable to initialize loaded project' ).console(whitespace=1).response if not write_results(response, project): return response.fail( code='PROJECT_WRITE_FAILURE', message='Unable to write project notebook data' ).console(whitespace=1).response runner.add_library_path(project.source_directory) return response.update( project=project.kernel_serialize() ).notify( kind='SUCCESS', code='PROJECT_OPENED', message='Opened project: {}'.format(path) ).console_header( project.title, level=2 ).console( """ PATH: {path} URL: {url} """.format(path=path, url=project.baked_url), whitespace=1 ).response