# aap

[![PyPI version](https://img.shields.io/pypi/v/aap2.svg)](https://pypi.org/project/aap2/)
[![Coverage](https://img.shields.io/badge/coverage-97.10%25-brightgreen.svg)](https://github.com/alexhagen/aap)

Automatic command-line interface generation for Python functions using type hints and docstrings.

## Overview

`aap` is a Python package that automatically generates command-line interfaces from Python functions. It leverages type hints to determine argument types and parses docstrings to provide helpful descriptions, eliminating the need to manually write argparse boilerplate code.

## Features

- Automatic CLI generation from function signatures
- Type inference from Python type hints (str, int, float, bool, List, Optional)
- Help text extraction from docstrings (Google, NumPy, and reStructuredText styles)
- Support for default values and optional parameters
- Boolean flags with automatic handling
- **Sequence mode** (list): Execute multiple functions in order with merged parameters
- **Command mode** (set): Create subcommands for different operations
- Automatic conflict resolution for parameter names in sequences
- Warning system for missing type annotations

## Installation

```bash
pip install aap2
```

Or with uv:

```bash
uv pip install aap2
```

## Quick Start

### Single Function CLI

```python
from aap import autoarg

def greet(name: str, age: int, greeting: str = "Hello"):
    """Greet a person.
    
    Args:
        name: Person's name
        age: Person's age
        greeting: Greeting message to use
    """
    return f"{greeting} {name}, you are {age} years old"

if __name__ == "__main__":
    cli = autoarg(greet)
    result = cli()
    print(result)
```

Run it:

```bash
python script.py --name Alice --age 30
# Output: Hello Alice, you are 30 years old

python script.py --name Bob --age 25 --greeting Hi
# Output: Hi Bob, you are 25 years old
```

### Sequence of Functions (List)

Execute multiple functions in order with a single flat CLI. All parameters are merged, and conflicting names are automatically prefixed with the function name.

```python
from aap import autoarg

def fetch_data(url: str, timeout: int = 30):
    """Fetch data from URL.
    
    Args:
        url: URL to fetch from
        timeout: Request timeout in seconds
    """
    return f"Fetched from {url}"

def process_data(format: str = "json"):
    """Process the fetched data.
    
    Args:
        format: Output format
    """
    return f"Processed as {format}"

if __name__ == "__main__":
    # Default: returns last result (unix-style)
    cli = autoarg([fetch_data, process_data])
    result = cli()
    print(result)
    
    # Or get all results as a list
    cli = autoarg([fetch_data, process_data], return_type="list")
    results = cli()
    print(results)  # [result1, result2]
    
    # Or get results as a dict
    cli = autoarg([fetch_data, process_data], return_type="dict")
    results = cli()
    print(results)  # {"fetch_data": result1, "process_data": result2}
```

Run it:

```bash
python script.py --url https://api.example.com --timeout 60 --format xml
# Executes both functions in order
```

#### Handling Parameter Conflicts

When multiple functions share parameter names, they are automatically prefixed:

```python
def step1(name: str, count: int):
    """First step."""
    return f"Step1: {name} x {count}"

def step2(name: str, value: float):
    """Second step."""
    return f"Step2: {name} = {value}"

cli = autoarg([step1, step2])
```

Run it:

```bash
python script.py --step1-name Alice --count 5 --step2-name Bob --value 3.14
```

### Command Mode (Set)

Create subcommands for different operations (like git, docker, etc.):

```python
from aap import autoarg

def add(a: int, b: int):
    """Add two numbers.
    
    Args:
        a: First number
        b: Second number
    """
    return a + b

def multiply(a: int, b: int):
    """Multiply two numbers.
    
    Args:
        a: First number
        b: Second number
    """
    return a * b

if __name__ == "__main__":
    # Use a set for commands
    cli = autoarg({add, multiply})
    result = cli()
    print(result)
```

Run it:

```bash
python script.py add --a 5 --b 3
# Output: 8

python script.py multiply --a 5 --b 3
# Output: 15
```

## Supported Types

- **Basic types**: `str`, `int`, `float`, `bool`
- **Optional types**: `Optional[T]` for optional parameters
- **List types**: `List[T]` for multiple values
- **Default values**: Automatically detected from function signatures

## Boolean Flags

Boolean parameters are automatically converted to flags:

```python
def process(name: str, verbose: bool = False, enabled: bool = True):
    """Process with flags.
    
    Args:
        name: Item name
        verbose: Enable verbose output
        enabled: Whether processing is enabled
    """
    pass
```

Usage:

```bash
# verbose defaults to False, use --verbose to set True
python script.py --name test --verbose

# enabled defaults to True, use --no-enabled to set False
python script.py --name test --no-enabled
```

## Docstring Support

`autoarg` supports multiple docstring formats:

### Google Style

```python
def func(param1: str, param2: int):
    """Function description.
    
    Args:
        param1: Description of param1
        param2: Description of param2
    """
    pass
```

### NumPy Style

```python
def func(param1: str, param2: int):
    """Function description.
    
    Parameters
    ----------
    param1 : str
        Description of param1
    param2 : int
        Description of param2
    """
    pass
```

### reStructuredText Style

```python
def func(param1: str, param2: int):
    """Function description.
    
    :param param1: Description of param1
    :param param2: Description of param2
    """
    pass
```

## Advanced Example

```python
from typing import List, Optional
from aap import autoarg

def backup_database(
    host: str,
    database: str,
    output_file: str,
    port: int = 5432,
    username: Optional[str] = None,
    compress: bool = True,
    tables: Optional[List[str]] = None
):
    """Backup a PostgreSQL database.
    
    Args:
        host: Database host
        database: Database name
        output_file: Output backup file path
        port: Database port
        username: Database username
        compress: Compress the backup file
        tables: Specific tables to backup
    """
    # Implementation here
    pass

if __name__ == "__main__":
    cli = autoarg(backup_database)
    cli()
```

Usage:

```bash
# Basic backup
python backup.py --host localhost --database mydb --output-file backup.sql

# Advanced backup with options
python backup.py --host db.example.com --database prod \
    --output-file backup.sql --port 3306 --username admin \
    --no-compress --tables users orders products
```

## Type Annotations

Type hints are required for proper CLI generation. If a parameter lacks a type annotation, `autoarg` will issue a warning and default to `str` type:

```python
def func(typed_param: int, untyped_param):
    pass
# Warning: Parameter 'untyped_param' has no type annotation. Defaulting to str type.
```

## Development

### Running Tests

```bash
pytest tests/ -v --cov=autoarg --cov-report=term-missing
```

### Building the Package

```bash
python -m build
```

## License

MIT License

## Contributing

Contributions are welcome. Please ensure all tests pass and maintain 100% code coverage for new features.

## Generative Artificial Intelligence Disclosure

This package was created using Cline and claude-sonnet-4-5 starting from the following prompt:

> Create a python package in this directory called aap. Auto arg parse has one functionality. That functionality is to take a callable or a list of callables and return another call, which wraps the input callable and uses python built-in argparse library to receive all input parameters to the call from command line input. Use the python type hints to figure out the type of the input parameters, and use the callable's documentation to figure out descriptions or help messages for the parameters. Throw a warning if a input parameter is not typed. If there are multiple callables passed to aap, create commands for each, with their own parameters parsed.
>
> Create a professional concise read me with no emojis.
>
> Create tests that use pytest and have a coverage of 100%.
>
> Create a pyproject.toml and package for pypi for distribution through pip or uv.
