Metadata-Version: 2.4
Name: paramify
Version: 0.2.0
Summary: A lightweight Python library for dynamic parameter management and runtime configuration.
Author-email: Ali PAikan <ali.paikan@gmail.com>
License: MIT License
        
        Copyright (c) 2025 LuxAI S.A.
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/luxai-qtrobot/paramify
Project-URL: Documentation, https://github.com/luxai-qtrobot/paramify#readme
Project-URL: Source, https://github.com/luxai-qtrobot/paramify
Project-URL: Bug Tracker, https://github.com/luxai-qtrobot/paramify/issues
Keywords: parameter,management,configuration,dynamic,runtime,UI
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ruamel.yaml>=0.17.0
Provides-Extra: web
Requires-Dist: bottle>=0.12; extra == "web"
Provides-Extra: dev
Requires-Dist: bottle>=0.12; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: coverage; extra == "dev"
Dynamic: license-file


# Paramify

![Test Status](https://github.com/luxai-qtrobot/paramify/actions/workflows/python-tests.yml/badge.svg)

**Paramify** is a lightweight Python library for dynamic parameter management. Define, validate, and manage parameters from a YAML or JSON schema with optional CLI integration and a web-based configuration UI.

---

## Key Features

- **Dynamic Parameter Management** — define parameters in YAML/JSON or as plain Python dicts
- **Groups & Nesting** — organise parameters hierarchically with `type: group`
- **YAML Includes** — split large configs across multiple files with `include:`
- **Typed Lists** — `list[bool]`, `list[int]`, `list[float]`, `list[double]`, `list[str]`, `list[string]`
- **C++-compatible type names** — `double` (alias for `float`) and `string` (alias for `str`)
- **`value:` keyword** — preferred over `default:` for setting parameter values (both accepted)
- **Scope-Based Control** — `scope: all | cli | runtime`; `runtime` params are never exposed to CLI
- **CLI Integration** — auto-generate CLI arguments from the schema; bool params get `--flag` / `--no-flag`
- **Custom Callbacks** — `on_<param>_set(self, value)` triggered on every update
- **Persistence** — changes written back to the source YAML/JSON file automatically
- **`save_as(path)`** — export current state to a new YAML file
- **Web Interface** — optional Bottle-based UI for runtime configuration (`pip install paramify[web]`)

![Paramify Web UI](https://github.com/luxai-qtrobot/paramify/raw/main/assets/ui-screenshot.png)

---

## Installation

```bash
# Core (parameter management + CLI)
pip install paramify

# With web interface
pip install paramify[web]
```

---

## Quick Start

### Basic usage

```python
from paramify import Paramify

class MyApp(Paramify):
    def on_speed_set(self, value):
        print(f"speed updated to {value}")

params = {
    "parameters": [
        {"name": "enabled", "type": "bool",   "default": True},
        {"name": "speed",   "type": "int",    "default": 10},
        {"name": "label",   "type": "string", "default": "robot"},
    ]
}

app = MyApp(params, enable_cli=False)
print(app.parameters.enabled)   # True
print(app.parameters.speed)     # 10

app.set_speed(42)               # triggers on_speed_set
print(app.get_parameters())     # {'enabled': True, 'speed': 42, 'label': 'robot'}
```

### Groups and nested parameters

Parameters can be organised into groups. Nested params are accessed with dot notation
(`params.camera.enabled`) and their setters/callbacks use underscores (`set_camera_enabled`,
`on_camera_enabled_set`).

```yaml
# config.yaml
name: "My Robot App"
parameters:

  - name: "enabled"
    type: "bool"
    value: true
    scope: "all"

  - name: "camera"
    type: "group"
    parameters:
      - name: "enabled"
        type: "bool"
        value: true
      - name: "fps"
        type: "int"
        value: 30
      - name: "gain"
        type: "double"       # alias for float
        value: 1.0
```

```python
from paramify import Paramify

class MyApp(Paramify):
    def on_camera_enabled_set(self, value):   # dots → underscores
        print(f"camera.enabled → {value}")

app = MyApp("config.yaml")

print(app.parameters.camera.enabled)   # True
print(app.parameters.camera.fps)       # 30

app.set_camera_enabled(False)          # triggers callback + saves file
app.set_camera_gain(2.5)
```

### YAML includes

Large configs can be split across files. Use `include:` inside a group to load parameters
from one or more external YAML files.

```yaml
# main.yaml
parameters:
  - name: "camera"
    type: "group"
    include: "camera_params.yaml"      # single file

  - name: "audio"
    type: "group"
    include:                           # list of files
      - "audio_base.yaml"
      - "audio_advanced.yaml"
```

```yaml
# camera_params.yaml
parameters:
  - name: "enabled"
    type: "bool"
    value: true
  - name: "resolution"
    type: "string"
    value: "1080p"
```

Included files are saved independently when their parameters change. Circular includes are
detected and raise a `ValueError`.

### Typed lists

```yaml
parameters:
  - name: "channels"
    type: "list[int]"
    value: [1, 2]

  - name: "labels"
    type: "list[string]"
    value: ["cat", "dog"]

  - name: "weights"
    type: "list[double]"
    value: [0.1, 0.9]
```

Supported list types: `list[bool]`, `list[int]`, `list[float]`, `list[double]`,
`list[str]`, `list[string]`, and untyped `list`.

### `value:` vs `default:`

Both keywords are accepted. `value:` takes precedence if both are present (matches
paramify-cpp behaviour).

```yaml
- name: "gain"
  type: "double"
  value: 2.0      # preferred
  default: 1.0    # fallback if value: is absent
```

### Scope

| Scope | CLI | Runtime |
|-------|-----|---------|
| `all` (default) | ✅ | ✅ |
| `cli` | ✅ | ❌ |
| `runtime` | ❌ | ✅ |

```yaml
- name: "debug"
  type: "bool"
  value: false
  scope: "runtime"   # never shown in --help, never parsed from argv
```

### `save_as`

Export the current parameter state to a new YAML file:

```python
app.save_as("/tmp/config_snapshot.yaml")
```

---

## YAML Configuration Reference

```yaml
name: "My App"                    # optional app name
description: "..."                # optional description shown in --help

parameters:

  # Scalar types
  - name: "flag"
    type: "bool"                  # bool | int | float | double | str | string
    value: true                   # 'value:' preferred; 'default:' also accepted
    scope: "all"                  # all | cli | runtime  (default: all)
    label: "Enable flag"          # display label (used in web UI)
    description: "..."            # shown in --help

  # Typed list
  - name: "ids"
    type: "list[int]"             # list[bool|int|float|double|str|string] or plain list
    value: [1, 2, 3]

  # Inline group
  - name: "camera"
    type: "group"
    parameters:
      - name: "enabled"
        type: "bool"
        value: true
      - name: "fps"
        type: "int"
        value: 30

  # Group loaded from external file(s)
  - name: "audio"
    type: "group"
    include: "audio_params.yaml"  # string or list of strings
```

---

## JSON Configuration

JSON configs support all scalar types and typed lists but **not** `type: group` or
`include:`. Use `"default"` as the value key in JSON.

```json
{
    "name": "My App",
    "parameters": [
        {"name": "enabled", "type": "bool",   "default": true},
        {"name": "speed",   "type": "int",    "default": 10},
        {"name": "labels",  "type": "list[string]", "default": ["a", "b"]}
    ]
}
```

---

## CLI Integration

Parameters with `scope: all` or `scope: cli` are automatically exposed as CLI arguments.

```bash
python my_app.py --speed 99 --camera-enabled --no-camera-mute
```

- Dot-notation keys become kebab-case flags: `camera.enabled` → `--camera-enabled`
- Bool params get both `--flag` and `--no-flag`
- List params accept multiple values: `--channels 1 2 3`
- CLI values override file defaults but are **not** persisted

---

## Web Interface

```bash
pip install paramify[web]
```

```python
from paramify.web import ParamifyWeb

class MyApp(ParamifyWeb):
    def on_speed_set(self, value):
        print(f"speed → {value}")

app = MyApp("config.yaml", host="0.0.0.0", port=5000)
input("Press Enter to stop...")
```

Open `http://localhost:5000` to adjust parameters in the browser. All groups (including
those loaded via `include:`) appear as a flat list in the UI.

---

## Callbacks and Setters

For a parameter with key `camera.enabled`:

| What | Name |
|------|------|
| Setter method | `set_camera_enabled(value)` |
| Callback method | `on_camera_enabled_set(self, value)` |
| Namespace access | `app.parameters.camera.enabled` |
| Dict access | `app.get_parameters()["camera.enabled"]` |

Dots in parameter keys are replaced by underscores in method names.

---

## Contributing

Contributions are welcome! Feel free to open issues or submit pull requests.

---

## License

This project is licensed under the MIT License. See the `LICENSE` file for details.
