Metadata-Version: 2.4
Name: pywsl2
Version: 0.1.0
Summary: Python interface for controlling WSL distributions and processes through the Windows WSL API (wslapi.h).
Author-email: "Michael K. Steinberg" <m.kuper.steinberg@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/Michael-K-Stein/pywsl2
Project-URL: Issues, https://github.com/Michael-K-Stein/pywsl2/issues
Keywords: wsl,wsl2,linux,subsystem,windows,wslapi
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: C++
Classifier: Topic :: System :: Operating System
Classifier: Topic :: System :: Systems Administration
Requires-Python: <3.16,>=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# pywsl2

## A Python wrapper for the [Windows Subsystem for Linux API (wslapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/wslapi/)

---

## Goal

**pywsl2** lets you control WSL distributions and the processes running inside them from Python, without shelling out to `wsl.exe` and
parsing its UTF-16 output. It calls the native WSL API directly, and its process interface feels like `subprocess`.

## How?

The design follows [pydia3](https://github.com/Michael-K-Stein/pydia):

* Every `wslapi.h` function is wrapped by a C++ function in **WslLib**, which handles all handle and memory management (pipes, process
  handles, `CoTaskMemFree`d configuration strings) and turns failing `HRESULT`s into exceptions.
* **pywsl2** (the native extension) exposes WslLib as [C-API Python](https://docs.python.org/3/c-api/) classes. It never calls the WSL API
  directly, and it releases the GIL around every blocking call.
* `wslapi.dll` is loaded lazily, so `import pywsl2` works on any Windows machine. If WSL is missing, the first API call raises
  `WslNotAvailableError`.

## Installation

pywsl2 is Windows-only (x64) and supports **CPython 3.12, 3.13, 3.14 and 3.15**. WSL must be installed (`wsl --install`).

```powershell
pip install pywsl2
```

## Usage

```python
import pywsl2

print(pywsl2.list_distributions())  # [pywsl2.Distribution('Ubuntu'), ...]
ubuntu = pywsl2.default_distribution()  # or pywsl2.Distribution("Ubuntu")

# Run a command to completion, like subprocess.run (output is always captured).
result = ubuntu.run("uname -a", text=True, check=True)
print(result.stdout)

# Feed it input; large inputs and outputs are pumped concurrently, so nothing deadlocks.
print(ubuntu.run("sort", input="b\na\n", text=True).stdout)

# Start a process and talk to it.
process = ubuntu.launch(
    "read name; echo hello $name", stdin=pywsl2.PIPE, stdout=pywsl2.PIPE
)
process.write(b"world\n")
print(process.read_stdout())  # b'hello world\n'
print(process.wait())  # 0

# Or let communicate() handle the I/O, with an optional timeout.
with ubuntu.launch("cat", stdin=pywsl2.PIPE, stdout=pywsl2.PIPE) as process:
    stdout, _ = process.communicate(b"data", timeout=10)

# Attach a command (or, with no command, the default shell) to the current console.
ubuntu.launch_interactive("htop")

# Inspect and change the distribution's configuration.
config = ubuntu.configuration
print(config.version, config.default_uid, config.flags, config.default_environment)
ubuntu.configure(flags=config.flags & ~pywsl2.DistributionFlags.APPEND_NT_PATH)
```

See [Examples/](Examples) for more.

### API overview

| `wslapi.h`                         | pywsl2                                                              |
|------------------------------------|---------------------------------------------------------------------|
| `WslIsDistributionRegistered`      | `Distribution.is_registered`                                        |
| `WslGetDistributionConfiguration`  | `Distribution.configuration` (and `version`, `default_uid`, `flags`, `default_environment`) |
| `WslConfigureDistribution`         | `Distribution.configure(default_uid=..., flags=...)`                |
| `WslLaunch`                        | `Distribution.launch(...)` → `Process`, `Distribution.run(...)`     |
| `WslLaunchInteractive`             | `Distribution.launch_interactive(...)`                              |
| `WslRegisterDistribution`          | `Distribution.register(name, tar_gz_path)`                          |
| `WslUnregisterDistribution`        | `Distribution.unregister()`                                         |
| *(registry, `HKCU\...\Lxss`)*      | `list_distributions()`, `default_distribution()`                    |

Streams passed to `launch()` are `None` (inherit this process's stream, the default), `PIPE` or `DEVNULL`; the values are the same as
in `subprocess`. Errors raise `WslError` (an `OSError` carrying `.hresult`), `DistributionNotFoundError`, `WslNotAvailableError` or
`TimeoutExpired` (also a `TimeoutError`).

### Limitations

* Windows only allows `WslRegisterDistribution` from a distribution's packaged launcher app. From a regular process it fails with
  `WslError` (`E_ACCESSDENIED`, 0x80070005). Use `wsl --import` to register a root file system from a script.
* `Process.pid` and `terminate()` refer to the Windows-side host process. Killing it ends the session, so the Linux command is
  terminated too.
* A `Process` is not safe to use from several threads at once, except that one thread may write to stdin while another reads stdout or
  stderr.

## Building from source

### Prerequisites

* Windows x64.
* Visual Studio 2022 or later (or the Build Tools) with the **Desktop development with C++** workload, which includes the Windows SDK
  that provides `wslapi.h`.
* CPython 3.12–3.15 x64.

### Build, install and test

```powershell
python -m pip install -e . pytest
python -m pytest                         # unit tests only
$env:PYWSL2_TEST_DISTRIBUTION = "Ubuntu" # also run integration tests inside this distribution
python -m pytest
```

To build a wheel instead: `python -m pip install build; python -m build --wheel`.

### Linting

```powershell
python -m pip install -r requirements-dev.txt
python -m ruff check .
python -m ruff format --check .
git ls-files '*.cpp' '*.h' | ForEach-Object { clang-format --dry-run --Werror $_ }
```
