Metadata-Version: 2.4
Name: pyyescrypt
Version: 0.2.1
Summary: Python bindings for yescrypt using a tiny Go shared library
License-Expression: GPL-3.0-or-later
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# pyyescrypt

Python bindings for yescrypt, implemented as a small Go shared library loaded via `ctypes`.

No CPython C extension code. The Python package is pure Python, plus a platform-specific shared library:

- macOS: `libyescrypt.dylib`
- Linux (glibc): `libyescrypt.so`

Windows is currently unsupported because the Go-based build has only been
validated on macOS and glibc-based Linux.

## Public API

```python
import pyyescrypt

hash_str = pyyescrypt.generate_hash("my password")
ok = pyyescrypt.verify_hash("my password", hash_str)
```

- `generate_hash(password: str) -> str` returns a modular-crypt yescrypt hash string (typically starts with `$y$`).
- `verify_hash(password: str, hash_str: str) -> bool` verifies a password against a stored hash string.

Errors raised from the native layer become `ValueError` in Python.

## Command-line interface

Installing `pyyescrypt` also provides a `pyyescrypt-cli` executable (packaged Go binary). It exposes the same operations as the Python module:

```bash
# Generate a hash (reads the password from stdin)
printf "secret" | pyyescrypt-cli generate

# Verify returns exit status 0 on success and prints "1"
printf "secret" | pyyescrypt-cli verify --hash '$y$...'
```

The Python package automatically falls back to this CLI whenever the shared library cannot be loaded (for example on musl-based systems), and you can force it via `PYYESCRYPT_BACKEND=cli`.

## Repo layout

- `capi/` Go code compiled as a C-shared library (`-buildmode=c-shared`)
- `src/pyyescrypt/_native.py` loads the shared library with `ctypes` and exposes the Python API
- `src/pyyescrypt/_native/` destination directory for the built shared library
- `tests/` Python tests (ABI-level tests via `ctypes`)

## Requirements

- Go toolchain available on PATH
- Python 3.10+ required
- On macOS, Xcode Command Line Tools installed (`xcode-select --install`)

## Build the native library

```bash
make native
```

This builds the shared library into:

- `src/pyyescrypt/_native/libyescrypt.dylib` on macOS
- `src/pyyescrypt/_native/libyescrypt.so` on Linux

To build the bundled CLI (used automatically on musl) run:

```bash
make cli
```

which outputs `src/pyyescrypt/_cli/pyyescrypt-cli`.

## Run tests

```bash
make test
```

This will:
- run `go test -cover ./...`
- create a local virtualenv at `.venv`
- install test dependencies into the venv
- build the native library
- run `pytest`

If you want only the Python tests:

```bash
make py-test
```

## Development notes

### Why ctypes instead of a CPython extension
CPython extension modules require a C ABI boundary anyway. This project avoids the CPython C-API completely by exporting a small C ABI from Go and calling it from Python using `ctypes`.

### Memory ownership
Native functions return `char*` allocated by the native library. Python must free them by calling `yc_free`. The Python wrapper handles this.

### Linux libc notes
glibc-based distros (manylinux wheels) are fully supported today. Musl-based
systems such as Alpine cannot load the Go-built shared library because Go
currently emits `initial-exec` TLS relocations for `-buildmode=c-shared`, which
musl's dynamic loader refuses. Installing the wheel on Alpine succeeds, but the
native backend fails to import.

The package now ships a small Go CLI binary that the Python module falls back
to automatically whenever the native backend cannot be loaded (or when
`PYYESCRYPT_BACKEND=cli` is set). Set `PYYESCRYPT_BACKEND=native` if you prefer
to fail fast when the shared library is unavailable.

### Packaging
The wheel must include the shared library under `pyyescrypt/_native/`.

Wheels use `py3-none-<platform>` tags: they contain no CPython extension and
have no Python-version-specific ABI. One wheel per OS, architecture, and libc
target can be installed on Python 3.10 and later, including Python 3.15,
without rebuilding for each Python minor version. They are not `any` wheels
because the bundled Go shared library and CLI are platform specific.

Cibuildwheel reuses each wheel across its available compatible CPython
interpreters for testing. Installation compatibility with a newer interpreter
does not replace running the tests on that interpreter.

For local development, `make native` places the library in the repo tree where the tests expect it.

To validate packaging, use a separate workflow:
1) build a wheel
2) install it into a clean venv
3) import `pyyescrypt` and run the tests against the installed package data

## License
See `LICENSE`.
