Metadata-Version: 2.4
Name: visionscaper-pybase
Version: 0.4.0
Summary: Basic functionality for Python projects: logging, base class, and validation utilities.
Author: Freddy Snijder
License: MIT
Project-URL: Homepage, https://github.com/visionscaper/pybase
Project-URL: Repository, https://github.com/visionscaper/pybase
Project-URL: Issues, https://github.com/visionscaper/pybase/issues
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.8
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 :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# pybase

Basic functionality for Python projects: logging setup, a `Base` class with built-in logger, validation utilities, and exception logging helpers.

## Installation

```bash
pip install visionscaper-pybase
```

## Logging

### Setup

Call `setup_logging()` at application startup to configure the root logger:

```python
from basics.logging import setup_logging, get_logger

setup_logging()

logger = get_logger('my_module')
logger.info('Application started')
```

Output:

```
20260209-151557.123 INFO: my_module::<module>: Application started
```

### Getting a Logger

Use `get_logger()` to create named loggers. The default log level is `DEBUG`, but can be overridden:

```python
from basics.logging import get_logger

# Module-level logger (common pattern)
module_logger = get_logger(__name__)

# Logger with custom log level
import logging
quiet_logger = get_logger('noisy_lib', log_level=logging.WARNING)
```

### Log Format

The default format includes a timestamp prefix:

```
YYYYMMDD-HHMMSS.msecs LEVEL: name::funcName: message
```

Example:

```
20260209-151557.123 INFO: train::main: Starting training
20260209-151557.456 WARNING: train::main: Low memory
```

### Environment Variables

| Variable | Default | Description |
|---|---|---|
| `PYBASE_LOG_PID` | unset | Set to `1` to prefix log lines with the process ID (`[PID]`) |
| `PYBASE_NO_TIMESTAMP_LOG` | unset | Set to `1` to disable the timestamp prefix |
| `PYBASE_SUPPRESS_LEGACY_WARNINGS` | unset | Set to `1` to suppress the deprecation warning about auto-configuration |

Example with PID enabled:

```
[4521] 20260209-151557.123 INFO: train::main: Starting training
```

### Custom Format

You can pass a custom format string and date format to `setup_logging()`:

```python
from basics.logging import setup_logging

setup_logging(
    format_string='%(asctime)s %(levelname)s %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
)
```

### Advanced: Building Format Strings

Use `build_logging_format()` to programmatically construct format strings with optional timestamp and PID, independent of environment variables:

```python
from basics.logging import build_logging_format

fmt = build_logging_format(include_timestamp=True, include_pid=True)
```

### Legacy Compatibility

Importing `basics.logging` currently configures the root logger automatically via `logging.basicConfig()`. This ensures existing code continues to work, but will be removed in a future version. New code should call `setup_logging()` explicitly.

Set `PYBASE_SUPPRESS_LEGACY_WARNINGS=1` to suppress the deprecation warning.

## Base Class

The `Base` class provides a built-in logger for any class that inherits from it:

```python
from basics.base import Base

class MyProcessor(Base):

    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)
        self._log.info(f'Processor created: {name}')

    def process(self, data):
        self._log.debug(f'Processing {len(data)} items')
        return [item * 2 for item in data]

proc = MyProcessor('example')
proc.process([1, 2, 3])
```

Output:

```
20260209-151557.123 INFO: MyProcessor::__init__: Processor created: example
20260209-151557.124 DEBUG: MyProcessor::process: Processing 3 items
```

The logger name defaults to the class name. Override it with `pybase_logger_name`:

```python
proc = MyProcessor('example', pybase_logger_name='custom_name')
# Logs will show: custom_name::__init__: ...
```

### Customizing the Logger

Subclasses can override `_pybase_get_logger_name()` to customize the logger name dynamically, or `_get_logger()` to use a different logger implementation entirely:

```python
class MyProcessor(Base):

    def __init__(self, name, **kwargs):
        self._name = name
        super().__init__(**kwargs)

    def _pybase_get_logger_name(self):
        return f'MyProcessor[{self._name}]'
```

## Exception Logging

Use `log_exception()` to log exceptions with their type and traceback through the logging system:

```python
from basics.logging import get_logger
from basics.logging_utils import log_exception

logger = get_logger(__name__)

try:
    result = 1 / 0
except Exception as e:
    log_exception(logger, 'Division failed', e)
```

## Validation Utilities

Type-checking helpers available from `basics.base_utils` or `basics.validation_utils`:

```python
from basics.base_utils import is_number, is_dict, is_sequence, is_callable

is_number(42)           # True
is_dict({'a': 1})       # True
is_sequence([1, 2, 3])  # True
is_callable(print)      # True
```

| Function | Description |
|---|---|
| `is_number(n)` | Instance of `numbers.Number` |
| `is_mapping(d)` | Instance of `collections.abc.Mapping` |
| `is_dict(d)` | Instance of `dict` |
| `is_bool(d)` | Instance of `bool` |
| `is_class(c)` | Instance of `type` |
| `is_sequence(l)` | Instance of `collections.abc.Sequence` |
| `is_non_empty_string(s)` | `str` with length > 0 |
| `is_file(fname)` | `os.path.isfile()` check |
| `is_callable(func)` | `callable()` check |
| `is_int_sequence(seq)` | Sequence where all elements are numbers |
| `sequences_are_compatible(a, b)` | Two sequences with equal length |

## License

MIT License. See [LICENSE](LICENSE) for details.
