Metadata-Version: 2.4
Name: pysae-cli-tools
Version: 0.1.7
Summary: Reusable utilities for Pysae Python CLIs (k8s pod dispatch, …).
License: MIT
Author: Rémi Alvergnat
Author-email: remi.alvergnat@pysae.com
Requires-Python: >=3.11,<4
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: typer (>=0.25.1,<0.26.0)
Project-URL: Homepage, https://gitlab.com/pysae/tools/cli-tools
Project-URL: Repository, https://gitlab.com/pysae/tools/cli-tools
Description-Content-Type: text/markdown

# pysae-cli-tools

Reusable utilities for Pysae Python CLIs.

[![PyPI](https://img.shields.io/pypi/v/pysae-cli-tools.svg)](https://pypi.org/project/pysae-cli-tools/)

## Installation

```bash
pip install pysae-cli-tools
# or
poetry add pysae-cli-tools
```

## What's included

### `pysae_cli_tools.k8s` — run any Typer command in an ephemeral pod

The `@k8s_support` decorator injects three flags into a Typer command —
`--k8s`, `--k8s-environment {dev|prod}`, `--k8s-from-local-sources` — and
dispatches the call into a freshly-spawned Kubernetes pod when `--k8s` is set.

It is meant for CLIs that need to run inside the same network as their target
infrastructure (private-link databases, VPC-only APIs, …) without rewriting the
command for `kubectl run`.

#### Usage with `build_k8s_support` (recommended)

Most projects share the same `K8sConfig` across every decorated command —
declare it once and reuse the bound decorator everywhere:

```python
from pathlib import Path

from typer import Typer

from pysae_cli_tools.k8s import K8sConfig, build_k8s_support

K8S_CONFIG = K8sConfig(
    default_image="<registry>/<project>:latest",
    project_root=Path(__file__).resolve().parents[1],
    copy_paths=("my_pkg", "pyproject.toml", "poetry.lock"),
    install_script=(
        "apt-get update -qq && pip install poetry && "
        "poetry config virtualenvs.create false && "
        "poetry install --only main --no-interaction"
    ),
    forwarded_envvars=("MY_API_KEY", "MY_DB_URI"),
    redacted_options=("--api-key", "--password"),
    env_secret_bindings={
        "dev": {"MONGO_URI": "k8s:secret:dev/dev-secrets:api-mongo-uri"},
        "prod": {"MONGO_URI": "k8s:secret:prod/prod-secrets:api-mongo-uri"},
    },
)

k8s_support = build_k8s_support(K8S_CONFIG)
app = Typer()


@app.command()
@k8s_support()
def my_command() -> None:
    ...


@app.command()
@k8s_support(pod_name_prefix="my-second-command")  # override per-command
def my_second_command() -> None:
    ...
```

#### Usage with the explicit form

When you want to use a different config per command, pass it directly:

```python
from pysae_cli_tools.k8s import K8sConfig, k8s_support

@app.command()
@k8s_support(config=K8S_CONFIG)
def my_command() -> None:
    ...
```

#### Per-environment secret bindings

`K8sConfig.env_secret_bindings` maps an environment value to a dict of
`envvar -> value-or-pattern`. Values can be either:

- a literal string forwarded verbatim into the pod, or
- a `k8s:secret:[<namespace>/]<secret-name>:<key>` reference resolved
  via `kubectl get secret` on the operator's machine before the pod is
  created (base64-decoded automatically).

Local environment wins: if the operator already exported the envvar
locally, that value is propagated as-is. Kubectl resolution is the
fallback, not the override. This matters for two reasons:

1. `Argument(envvar="X")` in Typer keeps working in both modes — the
   eager-inject hook seeds `os.environ` before Typer parses argv.
2. The operator can override a binding for a one-off run without
   editing the config.

Use `forwarded_envvars` for simple value-only propagation (no kubectl
fallback) and `env_secret_bindings` whenever you want the convenience
of pulling from a Kubernetes secret automatically.

#### What happens at runtime

When `--k8s` is set on the command line, the decorator:

1. Spawns an ephemeral pod using `K8sConfig.default_image` (or the Dockerfile
   base image when `--k8s-from-local-sources` is also set).
2. Forwards every envvar listed in `K8sConfig.forwarded_envvars` from your
   local shell into the pod's `env` block.
3. Runs `python -m <your.cli.module> <subcommand> <filtered argv>` inside the
   pod, with values matching `K8sConfig.redacted_options` masked in the
   `[K8S] Running:` log line.
4. Streams stdout/stderr back to your terminal and deletes the pod on exit.

See [`pysae_cli_tools/k8s/config.py`](pysae_cli_tools/k8s/config.py) for the
complete `K8sConfig` reference.

## Development

```bash
poetry install
poetry run pre-commit install
poetry run pytest
```

CI publishes a new version to PyPI on every push to `main` — see
[`.gitlab-ci.yml`](.gitlab-ci.yml). The version is computed from
`git describe` via `pysae_cli_tools.compute_version`.

