# Exposing Library Functions

## Basic Library Structure
```python
# src/mylib/__init__.py
"""MyLib - A helpful library."""

from .core import process_data, validate_input
from .exceptions import MyLibError, ValidationError
from .config import Config

__version__ = "1.0.0"
__all__ = [
    "process_data",
    "validate_input",
    "MyLibError",
    "ValidationError",
    "Config",
]
```

## Function Design Patterns

### 1. Clear Input/Output Types
```python
from typing import Optional, Union
from pathlib import Path

def process_file(
    filepath: Union[str, Path],
    encoding: str = "utf-8",
    strict: bool = False
) -> dict[str, Any]:
    """Process a file and return structured data.

    Args:
        filepath: Path to input file
        encoding: File encoding (default: utf-8)
        strict: Raise on warnings if True

    Returns:
        Dictionary with processed data

    Raises:
        FileNotFoundError: If file doesn't exist
        ValidationError: If file format is invalid
    """
    path = Path(filepath)
    if not path.exists():
        raise FileNotFoundError(f"File not found: {filepath}")

    # Process file...
    return {"status": "success", "records": 100}
```

### 2. Configuration Objects
```python
from dataclasses import dataclass
from typing import Optional

@dataclass
class ProcessConfig:
    """Configuration for data processing."""
    timeout: int = 30
    retries: int = 3
    batch_size: int = 100
    cache_enabled: bool = True

def process_data(data: list[dict], config: Optional[ProcessConfig] = None) -> list[dict]:
    """Process data with custom configuration."""
    config = config or ProcessConfig()

    # Use config.timeout, config.retries, etc.
    return processed_data
```

### 3. Context Managers
```python
from contextlib import contextmanager

@contextmanager
def database_connection(url: str):
    """Context manager for database connections."""
    conn = connect(url)
    try:
        yield conn
    finally:
        conn.close()

# Usage
with database_connection("postgresql://...") as conn:
    results = conn.query("SELECT * FROM users")
```

### 4. Builder Pattern
```python
class QueryBuilder:
    """Fluent interface for building queries."""

    def __init__(self):
        self._filters = []
        self._limit = None

    def filter(self, **kwargs) -> "QueryBuilder":
        self._filters.append(kwargs)
        return self

    def limit(self, n: int) -> "QueryBuilder":
        self._limit = n
        return self

    def build(self) -> Query:
        return Query(self._filters, self._limit)

# Usage
query = (QueryBuilder()
    .filter(status="active")
    .filter(age__gte=18)
    .limit(100)
    .build())
```

### 5. Async Support
```python
import asyncio
from typing import AsyncIterator

async def fetch_data_async(urls: list[str]) -> AsyncIterator[dict]:
    """Asynchronously fetch data from multiple URLs."""
    async with aiohttp.ClientSession() as session:
        for url in urls:
            async with session.get(url) as response:
                yield await response.json()

# Usage
async def main():
    async for data in fetch_data_async(urls):
        process(data)
```

## API Design Best Practices

### 1. Consistent Naming
```python
# Good: Consistent verb_noun pattern
def get_user(user_id: str) -> User
def create_user(data: dict) -> User
def update_user(user_id: str, data: dict) -> User
def delete_user(user_id: str) -> bool

# Bad: Inconsistent naming
def user(id: str)  # Ambiguous
def new_user(data: dict)  # Inconsistent with other verbs
def removeUser(id: str)  # camelCase in Python
```

### 2. Optional Dependencies
```python
# Make expensive imports optional
try:
    import pandas as pd
    HAS_PANDAS = True
except ImportError:
    HAS_PANDAS = False

def process_dataframe(df):
    """Process a pandas DataFrame."""
    if not HAS_PANDAS:
        raise ImportError(
            "pandas is required for this feature. "
            "Install with: pip install mylib[pandas]"
        )
    return df.apply(transform)
```

### 3. Backwards Compatibility
```python
import warnings
from typing import Optional

def old_function(data: dict) -> dict:
    """Deprecated: Use new_function instead."""
    warnings.warn(
        "old_function is deprecated, use new_function",
        DeprecationWarning,
        stacklevel=2
    )
    return new_function(data)

def new_function(data: dict, *, validate: bool = True) -> dict:
    """New improved function."""
    if validate:
        validate_data(data)
    return process(data)
```

## Testing Your API
```python
import pytest
from mylib import process_data, ValidationError

def test_process_data_valid_input():
    result = process_data({"key": "value"})
    assert result["status"] == "success"

def test_process_data_invalid_input():
    with pytest.raises(ValidationError) as exc_info:
        process_data({"invalid": None})
    assert "required field" in str(exc_info.value)

def test_process_data_with_config():
    from mylib import ProcessConfig
    config = ProcessConfig(timeout=60, retries=5)
    result = process_data(data, config=config)
    assert result is not None
```
