Metadata-Version: 2.4
Name: strcoerce
Version: 0.1.0
Summary: Safe string-to-type coercion — parse_bool, parse_int, parse_float, parse_list, parse_duration
Project-URL: Homepage, https://github.com/SpinnakerSix/strcoerce
Project-URL: Issues, https://github.com/SpinnakerSix/strcoerce/issues
Project-URL: Changelog, https://github.com/SpinnakerSix/strcoerce/blob/main/CHANGELOG.md
License: MIT
License-File: LICENSE
Keywords: bool,coerce,convert,environment,parse,string,type
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# strcoerce

Safe string-to-type coercion for Python. Zero dependencies.

## The problem

```python
import os
DEBUG = bool(os.environ.get("DEBUG", "false"))  # Always True — "false" is a non-empty string
```

`bool("false")` is `True` in Python. This catches experienced developers off guard and is a
[documented trap](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) with no
stdlib fix.

## Install

```
pip install strcoerce
```


## Usage

```python
from strcoerce import parse_bool, parse_int, parse_float, parse_list, parse_duration

# Environment variables
DEBUG  = parse_bool(os.environ.get("DEBUG", "false"))   # False
PORT   = parse_int(os.environ.get("PORT", "8080"))      # 8080
HOSTS  = parse_list(os.environ.get("ALLOWED_HOSTS", ""))  # []

# With defaults
parse_bool("maybe", default=False)  # False — no exception
parse_int("oops",   default=0)      # 0

# Durations
from datetime import timedelta
parse_duration("2h30m")   # timedelta(hours=2, minutes=30)
parse_duration("1w2d")    # timedelta(weeks=1, days=2)
```

## API

### `parse_bool(s, *, default=<raise>)`

| Input | Result |
|-------|--------|
| `"1"`, `"true"`, `"yes"`, `"on"`, `"t"`, `"y"` | `True` |
| `"0"`, `"false"`, `"no"`, `"off"`, `"f"`, `"n"` | `False` |
| anything else (no default) | `ParseError` |
| anything else (default given) | `default` |

Case-insensitive. Strips surrounding whitespace.

### `parse_int(s, *, default=<raise>, base=10)`

```python
parse_int("42")              # 42
parse_int("0xff", base=16)   # 255
parse_int("bad", default=0)  # 0
```

### `parse_float(s, *, default=<raise>)`

```python
parse_float("3.14")           # 3.14
parse_float("1e-3")           # 0.001
parse_float("bad", default=0) # 0
```

### `parse_list(s, sep=",", *, strip=True)`

```python
parse_list("a, b, c")          # ["a", "b", "c"]
parse_list("a|b|c", sep="|")   # ["a", "b", "c"]
parse_list("")                 # []
```

### `parse_duration(s)`

Parses combinations of `Nw Nd Nh Nm Ns` (weeks/days/hours/minutes/seconds).

```python
parse_duration("30s")        # timedelta(seconds=30)
parse_duration("5m")         # timedelta(minutes=5)
parse_duration("2h30m")      # timedelta(hours=2, minutes=30)
parse_duration("1w2d3h4m5s") # timedelta(weeks=1, days=2, hours=3, minutes=4, seconds=5)
```

## Error handling

All functions raise `ParseError` (subclass of `ValueError`) when input is invalid and no
default is given.

```python
from strcoerce import ParseError

try:
    val = parse_bool(user_input)
except ParseError as e:
    print(f"Invalid boolean: {e}")
```

## License

MIT
