def get_snapshot_listing(project: Project): """ :param project: :return: """ snapshots_directory = project.snapshot_path() if not os.path.exists(snapshots_directory): return [] out = [] for item in os.listdir(snapshots_directory): item_path = os.path.join(snapshots_directory, item) results_path = os.path.join(item_path, 'results.js') if not os.path.exists(results_path): continue out.append(dict( name=item, url=project.snapshot_url(item), directory=item_path, last_modified=os.path.getmtime(results_path) )) out = sorted(out, key=lambda x: x['last_modified']) return out
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 index_from_location(response: Response, project: Project, location: typing.Union[str, int] = None, default: int = None) -> int: """ :param response: :param project: :param location: :param default: :return: """ if location is None: return default if isinstance(location, (int, float)): return int(location) if isinstance(location, str): location = location.strip('"') try: location = int(location) return None if location < 0 else location except Exception: index = project.index_of_step(location) return default if index is None else (index + 1) return default
def index_from_location( response: Response, project: Project, location: typing.Union[str, int] = None, default: int = None ) -> int: """ :param response: :param project: :param location: :param default: :return: """ if location is None: return default if isinstance(location, (int, float)): return int(location) if isinstance(location, str): location = location.strip('"') try: location = int(location) return None if location < 0 else location except Exception: index = project.index_of_step(location) return default if index is None else (index + 1) return default
def initialize(project: typing.Union[str, Project]): """ :param project: :return: """ if isinstance(project, str): project = Project(source_directory=project) cauldron.project.load(project) return project
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 create_snapshot( project: Project, *args: typing.List[str], show: bool = True ): """ :param project: :param show: :return: """ if len(args) < 1: snapshot_name = datetime.now().strftime('%Y%b%d-%H-%M-%S') else: snapshot_name = args[0] snapshot_directory = project.snapshot_path() if not os.path.exists(snapshot_directory): os.makedirs(snapshot_directory) snapshot_name = snapshot_name.replace(' ', '-') snapshot_directory = project.snapshot_path(snapshot_name) environ.systems.remove(snapshot_directory) shutil.copytree(project.output_directory, snapshot_directory) url = project.snapshot_url(snapshot_name) environ.log_header('Snapshot URL', 5) environ.log( '* {}'.format(url), whitespace_bottom=1, indent_by=2 ) if show: webbrowser.open(url)
def open_snapshot(project: Project, name: str) -> dict: """ :param project: :param name: :return: """ snapshots_directory = project.snapshot_path() if not os.path.exists(snapshots_directory): return None item_path = os.path.join(snapshots_directory, name) results_path = os.path.join(item_path, 'results.js') if not os.path.exists(results_path): return None return dict( name=name, url=project.snapshot_url(name), directory=item_path, last_modified=os.path.getmtime(results_path) )
def create_step(response: Response, project: Project, name: str, position: typing.Union[str, int], title: str = None) -> Response: """ :param response: :param project: :param name: :param position: :param title: :return: """ name = name.strip('"') title = title.strip('"') if title else title index = index_from_location(response, project, position) if index is None: index = len(project.steps) name_parts = naming.explode_filename(name, project.naming_scheme) if not project.naming_scheme and not name_parts['name']: name_parts['name'] = naming.find_default_filename( [s.definition.name for s in project.steps]) name_parts['index'] = index name = naming.assemble_filename(scheme=project.naming_scheme, **name_parts) res = step_support.synchronize_step_names(project, index) response.consume(res) if response.failed: return response step_renames = res.returned step_data = {'name': name} if title: step_data['title'] = title result = project.add_step(step_data, index=index) if not os.path.exists(result.source_path): contents = ('import cauldron as cd\n\n' if result.source_path.endswith('.py') else '') with open(result.source_path, 'w+') as f: f.write(contents) project.save() project.write() index = project.steps.index(result) step_changes = [ dict(name=result.definition.name, filename=result.filename, action='added', step=writing.step_writer.serialize(result)._asdict(), after=None if index < 1 else project.steps[index - 1].definition.name) ] return response.update( project=project.kernel_serialize(), step_name=result.definition.name, step_path=result.source_path, step_changes=step_changes, step_renames=step_renames).notify( kind='CREATED', code='STEP_CREATED', message='"{}" step has been created'.format( result.definition.name)).console(whitespace=1).response
def create_step( response: Response, project: Project, name: str, position: typing.Union[str, int], title: str = None ) -> Response: """ :param response: :param project: :param name: :param position: :param title: :return: """ name = name.strip('"') title = title.strip('"') if title else title index = index_from_location(response, project, position) if index is None: index = len(project.steps) name_parts = naming.explode_filename(name, project.naming_scheme) if not project.naming_scheme and not name_parts['name']: name_parts['name'] = naming.find_default_filename( [s.definition.name for s in project.steps] ) name_parts['index'] = index name = naming.assemble_filename( scheme=project.naming_scheme, **name_parts ) res = step_support.synchronize_step_names(project, index) response.consume(res) if response.failed: return response step_renames = res.returned step_data = {'name': name} if title: step_data['title'] = title result = project.add_step(step_data, index=index) if not os.path.exists(result.source_path): contents = ( 'import cauldron as cd\n\n' if result.source_path.endswith('.py') else '' ) with open(result.source_path, 'w+') as f: f.write(contents) project.save() project.write() index = project.steps.index(result) step_changes = [dict( name=result.definition.name, filename=result.filename, action='added', step=writing.step_writer.serialize(result)._asdict(), after=None if index < 1 else project.steps[index - 1].definition.name )] return response.update( project=project.kernel_serialize(), step_name=result.definition.name, step_path=result.source_path, step_changes=step_changes, step_renames=step_renames ).notify( kind='CREATED', code='STEP_CREATED', message='"{}" step has been created'.format(result.definition.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 remove_snapshot( project: Project, *args: typing.List[str] ): """ :param project: :param args: :return: """ if len(args) < 1 or not args[0].strip(): environ.log( """ Are you sure you want to remove all snapshots in this project? """) if not query.confirm('Confirm Delete All', False): environ.log( '[ABORTED]: No snapshots were deleted', whitespace=1 ) return if not environ.systems.remove(project.snapshot_path()): environ.log( '[ERROR]: Failed to delete snapshots', whitespace=1 ) return environ.log( '[SUCCESS]: All snapshots have been removed', whitespace=1 ) return snapshot_name = args[0] environ.log( """ Are you sure you want to remove the snapshot "{}"? """.format(snapshot_name), whitespace=1 ) if not query.confirm('Confirm Deletion', False): environ.log( """ [ABORTED]: "{}" was not removed """.format(snapshot_name), whitespace=1 ) return if not environ.systems.remove(project.snapshot_path(snapshot_name)): environ.log( """ [ERROR]: Unable to delete snapshot "{}" at this time """.format(snapshot_name), whitespace=1 ) return environ.log( """ [SUCCESS]: Snapshot "{}" was removed """.format(snapshot_name), whitespace=1 ) return
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 run_local( context: cli.CommandContext, project: projects.Project, project_steps: typing.List[projects.ProjectStep], force: bool, continue_after: bool, single_step: bool, limit: int, print_status: bool, skip_library_reload: bool = False ) -> environ.Response: """ Execute the run command locally within this cauldron environment :param context: :param project: :param project_steps: :param force: :param continue_after: :param single_step: :param limit: :param print_status: :param skip_library_reload: Whether or not to skip reloading all project libraries prior to execution of the project. By default this is False in which case the project libraries are reloaded prior to execution. :return: """ skip_reload = ( skip_library_reload or environ.modes.has(environ.modes.TESTING) ) if not skip_reload: runner.reload_libraries() environ.log_header('RUNNING', 5) steps_run = [] if single_step: # If the user specifies the single step flag, only run one step. Force # the step to be run if they specified it explicitly ps = project_steps[0] if len(project_steps) > 0 else None force = force or (single_step and bool(ps is not None)) steps_run = runner.section( response=context.response, project=project, starting=ps, limit=1, force=force ) elif continue_after or len(project_steps) == 0: # If the continue after flag is set, start with the specified step # and run the rest of the project after that. Or, if no steps were # specified, run the entire project with the specified flags. ps = project_steps[0] if len(project_steps) > 0 else None steps_run = runner.complete( context.response, project, ps, force=force, limit=limit ) else: for ps in project_steps: steps_run += runner.section( response=context.response, project=project, starting=ps, limit=max(1, limit), force=force or (limit < 1 and len(project_steps) < 2), skips=steps_run + [] ) project.write() environ.log_blanks() step_changes = [] for ps in steps_run: step_changes.append(dict( name=ps.definition.name, action='updated', step=writing.step_writer.serialize(ps)._asdict() )) context.response.update(step_changes=step_changes) if print_status or context.response.failed: context.response.update(project=project.kernel_serialize()) return context.response
def run_local(context: cli.CommandContext, project: projects.Project, project_steps: typing.List[projects.ProjectStep], force: bool, continue_after: bool, single_step: bool, limit: int, print_status: bool) -> environ.Response: """ Execute the run command locally within this cauldron environment :param context: :param project: :param project_steps: :param force: :param continue_after: :param single_step: :param limit: :param print_status: :return: """ runner.reload_libraries() environ.log_header('RUNNING', 5) steps_run = [] if single_step: # If the user specifies the single step flag, only run one step. Force # the step to be run if they specified it explicitly ps = project_steps[0] if len(project_steps) > 0 else None force = force or (single_step and bool(ps is not None)) steps_run = runner.section(response=context.response, project=project, starting=ps, limit=1, force=force) elif continue_after or len(project_steps) == 0: # If the continue after flag is set, start with the specified step # and run the rest of the project after that. Or, if no steps were # specified, run the entire project with the specified flags. ps = project_steps[0] if len(project_steps) > 0 else None steps_run = runner.complete(context.response, project, ps, force=force, limit=limit) else: for ps in project_steps: steps_run += runner.section(response=context.response, project=project, starting=ps, limit=max(1, limit), force=force or (limit < 1 and len(project_steps) < 2), skips=steps_run + []) project.write() environ.log_blanks() step_changes = [] for ps in steps_run: step_changes.append( dict(name=ps.definition.name, action='updated', step=writing.step_writer.serialize(ps)._asdict())) context.response.update(step_changes=step_changes) if print_status or context.response.failed: context.response.update(project=project.kernel_serialize()) return context.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 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