Metadata-Version: 2.4
Name: vcti-session
Version: 1.0.1
Summary: Session lifecycle, working directories, and logging configuration for Python applications
Author: Visual Collaboration Technologies Inc.
Requires-Python: <3.15,>=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic
Requires-Dist: python-dotenv
Requires-Dist: vcti-logging>=1.0.5
Requires-Dist: vcti-config>=3.1.2
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Provides-Extra: lint
Requires-Dist: ruff; extra == "lint"
Dynamic: license-file

# Session Management

## Purpose

VCollab applications need a consistent way to manage execution
sessions — creating work directories, configuring logging, and
cleaning up after completion. The `vcti-session` package provides
session lifecycle management with automatic directory creation,
configurable cleanup policies, and integrated logging setup.

**Dependencies:** pydantic, python-dotenv, vcti-logging, vcti-config

---

## Installation

```bash
# Latest main branch
pip install vcti-session



### In `requirements.txt`

```
vcti-session>=1.0.1
```

### In `pyproject.toml` dependencies

```toml
dependencies = [
    "vcti-session>=1.0.1",
]
```

---

## Quick Start

### Auto-managed session directory

```python
from pathlib import Path
from vcti.session import SessionDir, SessionHandler, LogConfig

session_dir = SessionDir(
    base_directory=Path("/tmp/sessions"),
    max_session_dirs=10,
    max_age_days=7,
)
log_config = LogConfig(file_path="logs/app.log", console_enabled=True)

handler = SessionHandler(session_dir, log_config)
with handler.session_context() as session:
    print(f"Working in: {session.path}")
    # Session directory auto-created with unique name
    # Logging configured to session_path/logs/app.log
# Old sessions pruned automatically on exit
```

### User-specified work directory

```python
from vcti.session import WorkDir, LogConfig

work_dir = WorkDir(
    directory="/path/to/my/work",
    log_config=LogConfig(file_path="app.log"),
    create_if_missing=True,
)
with work_dir.work_context():
    print(f"Working in: {work_dir.path}")
```

### Configuration from environment variables

```python
# SessionDir reads: SESSION_DIR_PREFIX, SESSION_DIR_MAX_SESSION_DIRS, etc.
session_dir = SessionDir(
    base_directory="/tmp/sessions",
    env_prefix="MYAPP_",     # reads MYAPP_SESSION_DIR_PREFIX, etc.
    env_file=".env",         # load from .env file
)

# LogConfig reads: SESSION_LOG_FILE_PATH, SESSION_LOG_LEVEL, etc.
log_config = LogConfig(
    env_prefix="MYAPP_",
    default_file_path="logs/app.log",
    default_level="info",
)
```

### Lifecycle hooks

`SessionHandler` supports optional callbacks at key lifecycle points:

```python
def archive_sessions(dirs: list[Path]):
    for d in dirs:
        upload_to_s3(d)

def notify_created(path: Path):
    print(f"Session started: {path}")

def on_exit(path: Path, error: Exception | None):
    duration = time.time() - start_time
    log_to_db(path, duration, error)

handler = SessionHandler(
    session_dir, log_config,
    on_session_created=notify_created,
    on_session_exit=on_exit,
    on_before_prune=archive_sessions,
    on_session_error=lambda exc: alert_oncall(exc),
)
```

| Hook | Signature | When |
|------|-----------|------|
| `on_session_created` | `(path: Path) -> None` | After directory created and logging set up |
| `on_session_exit` | `(path: Path, error: Exception \| None) -> None` | When context exits, before pruning |
| `on_before_prune` | `(dirs: list[Path]) -> None` | Before old sessions are deleted |
| `on_session_error` | `(error: Exception) -> None` | When an exception occurs in context |

All hooks are optional (default `None`). A broken hook is logged as a
warning and never crashes the session.

---

## Key Classes

| Class | Purpose |
|-------|---------|
| `SessionDir` | Creates uniquely-named session directories with cleanup policies (by count and age) |
| `WorkDir` | Manages a user-specified directory without auto-naming or cleanup |
| `SessionHandler` | Context manager combining SessionDir + LogConfig for full session lifecycle |
| `LogConfig` | Logging configuration with multi-source resolution (args > env > defaults) |

---

## Configuration Priority

All configurable values follow this resolution order:

1. **Direct constructor arguments** (highest priority)
2. **Environment variables** (with optional prefix)
3. **Default values** (lowest priority)

---

## Environment Variables

All environment variable names follow the pattern `{env_prefix}SESSION_DIR_*`
or `{env_prefix}SESSION_LOG_*`. The `env_prefix` is set via the constructor
(default: empty string).

### SessionDir

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SESSION_DIR_PREFIX` | str | `session_` | Prefix for session directory names |
| `SESSION_DIR_TIMESTAMP_FORMAT` | str | `%Y-%m-%d-%H-%M-%S` | strftime format for directory timestamp |
| `SESSION_DIR_MAX_SESSION_DIRS` | int | `20` | Maximum session directories to keep (`-1` = unlimited) |
| `SESSION_DIR_MAX_AGE_DAYS` | int | `10` | Maximum age in days before deletion (`-1` = unlimited) |

### LogConfig

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SESSION_LOG_FILE_PATH` | path | `None` | Log file path (relative or absolute). None disables file logging. |
| `SESSION_LOG_LEVEL` | str | `warning` | Minimum log level (`debug`/`info`/`warning`/`error`/`critical`) |
| `SESSION_LOG_CONSOLE_ENABLED` | bool | `false` | Duplicate logs to stdout/stderr |
| `SESSION_LOG_COLOR_ENABLED` | bool | `false` | Enable ANSI color codes in console output |
| `SESSION_LOG_MESSAGE_FORMAT` | str | *(none)* | Custom log message format string |

With a prefix like `env_prefix="MYAPP_"`, variables become
`MYAPP_SESSION_DIR_PREFIX`, `MYAPP_SESSION_LOG_LEVEL`, etc.

---

## Logging

The package uses `vcti.logging` for two distinct purposes:

### 1. Internal operational logging

The package logs its own operations (directory created, pruning started,
errors encountered) via a standard logger at `debug`/`verbose`/`warning`
levels.  This is always active and controlled via Python's standard
`logging` configuration — set the level on the `vcti.session` logger
namespace to see or suppress these messages.

### 2. User session logging (via LogConfig)

`LogConfig` optionally configures file and console logging **for the
user's session**.  This is fully opt-in:

```python
# Batch mode — no logging setup (default)
handler = SessionHandler(session_dir)

# File logging only
handler = SessionHandler(session_dir, LogConfig(file_path="app.log"))

# Console only (interactive use)
handler = SessionHandler(session_dir, LogConfig(console_enabled=True))

# Both file and console
handler = SessionHandler(
    session_dir,
    LogConfig(
        file_path="logs/app.log",
        console_enabled=True,
        color_enabled=True,
        level="debug",
    ),
)

# Skip LogConfig entirely — handle logging via hooks
handler = SessionHandler(
    session_dir,
    on_session_created=lambda path: my_custom_logging_setup(path),
)
```

When `log_config` is `None` (the default), no file handlers are created,
no log directories are created, and no console handlers are configured.
The session directory is still created and managed normally.

When `file_path` is set as a relative path (e.g., `"logs/app.log"`), it
is resolved relative to the session directory, and the parent directories
are created automatically.  Absolute paths are used as-is.

---

## Thread Safety

By default, `SessionHandler` and `WorkDir` call `os.chdir()` to change the
process working directory. This is **not thread-safe** — `os.chdir()` affects
the entire process.

For multi-threaded applications, set `change_directory=False`:

```python
# Thread-safe: cwd is never changed
handler = SessionHandler(session_dir, log_config, change_directory=False)
with handler.session_context() as session:
    # Use session.path directly instead of relying on cwd
    output_file = session.path / "results.json"
    output_file.write_text('{"status": "ok"}')
```

The same parameter is available on `WorkDir`:

```python
work_dir = WorkDir(
    directory="/shared/work",
    log_config=log_config,
    change_directory=False,
)
with work_dir.work_context() as wd:
    data_file = wd.path / "data.csv"
```

**When to use which:**

| Scenario | `change_directory` |
|---|---|
| Single-threaded scripts | `True` (default) — simpler, relative paths work naturally |
| Multi-threaded applications | `False` — use `session.path` for all file operations |
| Async applications | `False` — `os.chdir()` is not async-safe |

---

## Public API

| Symbol | Purpose |
|--------|---------|
| `SessionDir` | Session directory creation and pruning |
| `WorkDir` | User-specified directory management |
| `SessionHandler` | Full session lifecycle context manager |
| `LogConfig` | Logging configuration model |

---

## Documentation

- [Design](docs/design.md) — Architecture decisions and configuration model
- [Source Guide](docs/source-guide.md) — File descriptions and execution flows
- [API Reference](docs/api.md) — Autodoc for all modules
