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