Usage

Specifying Files And Directories To Convert

To convert a single file:

$ cat myscript.py
print(hello := 'world')
$ walrus myscript.py
Now converting: '/path/to/project/myscript.py'
$ cat myscript.py  # file overwritten with conversion result
if False:
    hello = NotImplemented


def _walrus_wrapper_hello_5adbf5ee911449cba75e35b9ef97ea80(expr):
    """Wrapper function for assignment expression."""
    global hello
    hello = expr
    return hello


print(_walrus_wrapper_hello_5adbf5ee911449cba75e35b9ef97ea80('world'))

To convert the whole project at the current working directory (overwrites all Python source files inside):

$ walrus .

Multiple files and directories may be supplied at the same time:

$ walrus script_without_py_extension /path/to/another/project

Note

When converting a directory, walrus will recursively find all the Python source files in the directory (and its subdirectories, if any). Whether a file is a Python source file is determined by its file extension (.py or .pyw). If you want to convert a file without a Python extension, you will need to explicitly specify it in the argument list.

If you prefer a side-effect free behavior (do not overwrite files), you can use the simple mode.

Simple mode with no arguments (read from stdin, write to stdout):

$ printf 'print(hello := "world")\n' | python3 walrus.py -s
if False:
    hello = NotImplemented


def _walrus_wrapper_hello_fbf3a9dabd2b40348815e3f2b22a1683(expr):
    """Wrapper function for assignment expression."""
    global hello
    hello = expr
    return hello

print(_walrus_wrapper_hello_fbf3a9dabd2b40348815e3f2b22a1683("world"))

Simple mode with a file name argument (read from file, write to stdout):

$ cat myscript.py
print(hello := 'world')
$ walrus -s myscript.py
if False:
    hello = NotImplemented


def _walrus_wrapper_hello_d1e6c2a11a76400aa9745bd90b3fb52a(expr):
    """Wrapper function for assignment expression."""
    global hello
    hello = expr
    return hello

print(_walrus_wrapper_hello_d1e6c2a11a76400aa9745bd90b3fb52a('world'))
$ cat myscript.py
print(hello := 'world')

In simple mode, no file names shall be provided via positional arguments.

Archiving And Recovering Files

If you are not using the simple mode, walrus overwrites Python source files, which could potentially cause data loss. Therefore, a built-in archiving functionality is enabled by default. The original copies of the Python source files to be converted will be packed into an archive file and placed under the archive subdirectory of the current working directory.

To opt out of archiving, use the CLI option -na (--no-archive), or set environment variable WALRUS_DO_ARCHIVE=0.

To use an alternative name for the archive directory (other than archive), use the CLI option -k (--archive-path), or set the environment variable WALRUS_ARCHIVE_PATH.

To recover files from an archive file, use the CLI option -r (--recover):

$ walrus -r archive/archive-20200814222751-f3a514d40d69c6d5.tar.gz
Recovered files from archive: 'archive/archive-20200814222751-f3a514d40d69c6d5.tar.gz'
$ ls archive/
archive-20200814222751-f3a514d40d69c6d5.tar.gz

By default, the archive file is still retained after recovering from it. If you would like it to be removed after recovery, specify the CLI option -r2:

$ walrus -r archive/archive-20200814222751-f3a514d40d69c6d5.tar.gz -r2
Recovered files from archive: 'archive/archive-20200814222751-f3a514d40d69c6d5.tar.gz'
$ ls archive/

If you also want to remove the archive directory if it becomes empty, specify the CLI option -r3:

$ walrus -r archive/archive-20200814222751-f3a514d40d69c6d5.tar.gz -r3
Recovered files from archive: 'archive/archive-20200814222751-f3a514d40d69c6d5.tar.gz'
$ ls archive/
ls: cannot access 'archive/': No such file or directory

Warning

To improve stability of file recovery, the archive file records the original absolute paths of the Python source files. Thus, file recovery is only guaranteed to work correctly on the same machine where the archive file was created. Never perform the recovery operation on an arbitrary untrusted archive file. Doing so may allow attackers to overwrite any files in the system.

Conversion Options

By default, walrus automatically detects file line endings and use the same line ending for code insertion. If you want to manually specify the line ending to be used, use the CLI option -l (--linesep) or the WALRUS_LINESEP environment variable.

By default, walrus automatically detects file indentations and use the same indentation for code insertion. If you want to manually specify the indentation to be used, use the CLI option -t (--indentation) or the WALRUS_INDENTATION environment variable.

By default, walrus parse Python source files as the latest version. If you want to manually specify a version for parsing, use the CLI option -vs (-vf, --source-version, --from-version) or the WALRUS_SOURCE_VERSION environment variable.

By default, code insertion of walrus conforms to PEP 8. To opt out and get a more compact result, specify the CLI option -n8 (--no-pep8) or set environment variable WALRUS_PEP8=0.

Runtime Options

Specify the CLI option -q (--quiet) or set environment variable WALRUS_QUIET=1 to run in quiet mode.

Specify the CLI option -C (--concurrency) or set environment variable WALRUS_CONCURRENCY to specify the number of concurrent worker processes for conversion.

Use the --dry-run CLI option to list the files to be converted without actually performing conversion and archiving.

By running walrus --help, you can see the current values of all the options, based on their default values and your environment variables.

API Usage

If you want to programmatically invoke walrus, you may want to look at API Reference. The walrus.convert() and walrus.walrus() functions should be most commonly used.

Distutils/Setuptools Integration

walrus can also be directly integrated within your setup.py script to dynamically convert assignment expressions upon installation:

import subprocess
import sys

try:
    from setuptools import setup
    from setuptools.command.build_py import build_py
except ImportError:
    from distutils.core import setup
    from distutils.command.build_py import build_py

version_info = sys.version_info[:2]


class build(build_py):
    """Add on-build backport code conversion."""

    def run(self):
        if version_info < (3, 8):
            try:
                subprocess.check_call(  # nosec
                    [sys.executable, '-m', 'walrus', '--no-archive', 'PACKAGENAME']
                )
            except subprocess.CalledProcessError as error:
                print('Failed to perform assignment expression backport compiling.'
                      'Please consider manually install `bpc-walrus` and try again.', file=sys.stderr)
                sys.exit(error.returncode)
        build_py.run(self)


setup(
    ...
    setup_requires=[
        'bpc-walrus; python_version < "3.8"',
    ],
    cmdclass={
        'build_py': build,
    },
)

Or, as PEP 518 proposed, you may simply add bpc-walrus to the requires list from the [build-system] section in the pyproject.toml file:

[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools", "wheel", "bpc-walrus"]  # PEP 508 specifications.
...