wvpy.pysheet
1# run with: 2# python -m wvpy.pysheet test.py 3# python -m wvpy.pysheet test.ipynb 4 5from typing import Iterable 6import argparse 7import os 8import shutil 9import sys 10import traceback 11from wvpy.jtools import convert_py_file_to_notebook, convert_notebook_file_to_py 12 13 14def pysheet( 15 infiles: Iterable[str], 16 *, 17 quiet: bool = False, 18 delete: bool = False, 19 black: bool = False, 20) -> int: 21 """ 22 Convert between .ipynb and .py files. 23 24 :param infiles: list of file names to process 25 :param quiet: if True do the work quietly 26 :param delete: if True, delete input 27 :param black: if True, use black to re-format Python code cells 28 :return: 0 if successful 29 """ 30 # some pre-checks 31 assert not isinstance(infiles, str) # common error 32 infiles = list(infiles) 33 assert len(infiles) > 0 34 assert len(set(infiles)) == len(infiles) 35 assert isinstance(quiet, bool) 36 assert isinstance(delete, bool) 37 assert isinstance(black, bool) 38 # set up the work request 39 base_names_seen = set() 40 input_suffices_seen = set() 41 tasks = [] 42 other_suffix = {'.py': '.ipynb', '.ipynb': '.py'} 43 for input_file_name in infiles: 44 assert isinstance(input_file_name, str) 45 assert len(input_file_name) > 0 46 suffix_seen = 'error' # placeholder/sentinel 47 base_name = input_file_name 48 if input_file_name.endswith('.py'): 49 suffix_seen = '.py' 50 base_name = input_file_name.removesuffix(suffix_seen) 51 elif input_file_name.endswith('.ipynb'): 52 suffix_seen = '.ipynb' 53 base_name = input_file_name.removesuffix(suffix_seen) 54 else: 55 py_exists = os.path.exists(input_file_name + '.py') 56 ipynb_exists = os.path.exists(input_file_name + '.ipynb') 57 if py_exists == ipynb_exists: 58 raise ValueError(f'{base_name}: if no suffix is specified, then exactly one of the .py or ipynb file forms must be present') 59 if py_exists: 60 suffix_seen = '.py' 61 else: 62 suffix_seen = '.ipynb' 63 input_file_name = input_file_name + suffix_seen 64 assert os.path.exists(input_file_name) 65 assert suffix_seen in other_suffix.keys() # expected suffix 66 assert base_name not in base_names_seen # each base file name only used once 67 base_names_seen.add(base_name) 68 input_suffices_seen.add(suffix_seen) 69 if len(input_suffices_seen) != 1: # only one direction of conversion in batch job 70 raise ValueError(f"conversion job may only have one input suffix: {input_suffices_seen}") 71 output_file_name = base_name + other_suffix[suffix_seen] 72 tasks.append((input_file_name, output_file_name)) 73 # do the work 74 for input_file_name, output_file_name in tasks: 75 if not quiet: 76 print(f'from "{input_file_name}" to "{output_file_name}"') 77 # back up result target if present 78 if os.path.exists(output_file_name): 79 output_backup_file = f'{output_file_name}~' 80 if not quiet: 81 print(f' copying previous output target "{output_file_name}" to "{output_backup_file}"') 82 shutil.copy2(output_file_name, output_backup_file) 83 # convert 84 if input_file_name.endswith('.py'): 85 if not quiet: 86 print(f" converting Python {input_file_name} to Jupyter notebook {output_file_name}") 87 convert_py_file_to_notebook( 88 py_file=input_file_name, 89 ipynb_file=output_file_name, 90 use_black=black, 91 ) 92 elif input_file_name.endswith('.ipynb'): 93 if not quiet: 94 print(f' converting Jupyter notebook "{input_file_name}" to Python "{output_file_name}"') 95 convert_notebook_file_to_py( 96 ipynb_file=input_file_name, 97 py_file=output_file_name, 98 use_black=black, 99 ) 100 else: 101 raise ValueError("input file name must end with .py or .ipynb") 102 # do any deletions 103 if delete: 104 input_backup_file = f'{input_file_name}~' 105 if not quiet: 106 print(f" moving input {input_file_name} to {input_backup_file}") 107 try: 108 os.remove(input_backup_file) 109 except FileNotFoundError: 110 pass 111 os.rename(input_file_name, input_backup_file) 112 if not quiet: 113 print() 114 return 0 115 116 117if __name__ == '__main__': 118 try: 119 parser = argparse.ArgumentParser(description="Convert between .py and .ipynb or back (can have suffix, or guess suffix)") 120 parser.add_argument('--quiet', action='store_true', help='quite operation') 121 parser.add_argument('--delete', action='store_true', help='delete input file') 122 parser.add_argument('--black', action='store_true', help='use black to re-format cells') 123 parser.add_argument( 124 'infile', 125 metavar='infile', 126 type=str, 127 nargs='+', 128 help='name of input file(s)') 129 args = parser.parse_args() 130 # some pre-checks 131 assert len(args.infile) > 0 132 assert len(set(args.infile)) == len(args.infile) 133 assert isinstance(args.quiet, bool) 134 assert isinstance(args.delete, bool) 135 assert isinstance(args.black, bool) 136 ret = pysheet( 137 infiles=args.infile, 138 quiet=args.quiet, 139 delete=args.delete, 140 black=args.black, 141 ) 142 sys.exit(ret) 143 except AssertionError: 144 _, _, tb = sys.exc_info() 145 tb_info = traceback.extract_tb(tb) 146 filename, line, func, text = tb_info[-1] 147 print(f'Assertion failed {filename}:{line} (caller {func}) in statement {text}') 148 except Exception as ex: 149 print(ex) 150 sys.exit(-1)
def
pysheet( infiles: Iterable[str], *, quiet: bool = False, delete: bool = False, black: bool = False) -> int:
16def pysheet( 17 infiles: Iterable[str], 18 *, 19 quiet: bool = False, 20 delete: bool = False, 21 black: bool = False, 22) -> int: 23 """ 24 Convert between .ipynb and .py files. 25 26 :param infiles: list of file names to process 27 :param quiet: if True do the work quietly 28 :param delete: if True, delete input 29 :param black: if True, use black to re-format Python code cells 30 :return: 0 if successful 31 """ 32 # some pre-checks 33 assert not isinstance(infiles, str) # common error 34 infiles = list(infiles) 35 assert len(infiles) > 0 36 assert len(set(infiles)) == len(infiles) 37 assert isinstance(quiet, bool) 38 assert isinstance(delete, bool) 39 assert isinstance(black, bool) 40 # set up the work request 41 base_names_seen = set() 42 input_suffices_seen = set() 43 tasks = [] 44 other_suffix = {'.py': '.ipynb', '.ipynb': '.py'} 45 for input_file_name in infiles: 46 assert isinstance(input_file_name, str) 47 assert len(input_file_name) > 0 48 suffix_seen = 'error' # placeholder/sentinel 49 base_name = input_file_name 50 if input_file_name.endswith('.py'): 51 suffix_seen = '.py' 52 base_name = input_file_name.removesuffix(suffix_seen) 53 elif input_file_name.endswith('.ipynb'): 54 suffix_seen = '.ipynb' 55 base_name = input_file_name.removesuffix(suffix_seen) 56 else: 57 py_exists = os.path.exists(input_file_name + '.py') 58 ipynb_exists = os.path.exists(input_file_name + '.ipynb') 59 if py_exists == ipynb_exists: 60 raise ValueError(f'{base_name}: if no suffix is specified, then exactly one of the .py or ipynb file forms must be present') 61 if py_exists: 62 suffix_seen = '.py' 63 else: 64 suffix_seen = '.ipynb' 65 input_file_name = input_file_name + suffix_seen 66 assert os.path.exists(input_file_name) 67 assert suffix_seen in other_suffix.keys() # expected suffix 68 assert base_name not in base_names_seen # each base file name only used once 69 base_names_seen.add(base_name) 70 input_suffices_seen.add(suffix_seen) 71 if len(input_suffices_seen) != 1: # only one direction of conversion in batch job 72 raise ValueError(f"conversion job may only have one input suffix: {input_suffices_seen}") 73 output_file_name = base_name + other_suffix[suffix_seen] 74 tasks.append((input_file_name, output_file_name)) 75 # do the work 76 for input_file_name, output_file_name in tasks: 77 if not quiet: 78 print(f'from "{input_file_name}" to "{output_file_name}"') 79 # back up result target if present 80 if os.path.exists(output_file_name): 81 output_backup_file = f'{output_file_name}~' 82 if not quiet: 83 print(f' copying previous output target "{output_file_name}" to "{output_backup_file}"') 84 shutil.copy2(output_file_name, output_backup_file) 85 # convert 86 if input_file_name.endswith('.py'): 87 if not quiet: 88 print(f" converting Python {input_file_name} to Jupyter notebook {output_file_name}") 89 convert_py_file_to_notebook( 90 py_file=input_file_name, 91 ipynb_file=output_file_name, 92 use_black=black, 93 ) 94 elif input_file_name.endswith('.ipynb'): 95 if not quiet: 96 print(f' converting Jupyter notebook "{input_file_name}" to Python "{output_file_name}"') 97 convert_notebook_file_to_py( 98 ipynb_file=input_file_name, 99 py_file=output_file_name, 100 use_black=black, 101 ) 102 else: 103 raise ValueError("input file name must end with .py or .ipynb") 104 # do any deletions 105 if delete: 106 input_backup_file = f'{input_file_name}~' 107 if not quiet: 108 print(f" moving input {input_file_name} to {input_backup_file}") 109 try: 110 os.remove(input_backup_file) 111 except FileNotFoundError: 112 pass 113 os.rename(input_file_name, input_backup_file) 114 if not quiet: 115 print() 116 return 0
Convert between .ipynb and .py files.
Parameters
- infiles: list of file names to process
- quiet: if True do the work quietly
- delete: if True, delete input
- black: if True, use black to re-format Python code cells
Returns
0 if successful