Adapters

The adapters module wraps external tools and APIs behind consistent Python interfaces.

Overview

  • Wrap subprocess calls, COM automation, or REST APIs.

  • ShellAdapter.exe() returns (stderr, stdout) tuples for standard shell operations.

  • UvToolManager.run() exits the process directly after execution.

  • Handle platform-specific differences internally.

  • Log operations via the standard logging module.

Adapters differ in how they return results: shell wrappers yield (stderr, stdout) for post-processing, while UvToolManager terminates the process as part of its flow.

Return Convention

stderr, stdout = adapter.exe("command", working_dir)
if stderr:
    # Handle error
# Process stdout

When to Use

Choosing the Right Adapter

Adapter Selection Guide

Scenario

Adapter

Rationale

New projects, fast installs

UvAdapter / UvToolManager

uv is faster than Poetry, better for CI/CD

Running arbitrary shell commands

ShellAdapter

Handles aliases, env vars, logging

JIRA issue creation

JiraAdapter

Typed DTO, handles custom field quirks

Windows Outlook calendar

OutlookLocalAdapter

COM automation for local Outlook

Styled terminal output

ConsoleAdapter

Rich formatting, spinners, confirmations

ShellAdapter vs Specific Adapters

Specific Adapters (JiraAdapter, OutlookLocalAdapter)

ShellAdapter

Type-safe interfaces via DTOs

One-off commands without dedicated adapters

API-specific error handling

Scripts requiring alias expansion

Protocol abstraction (REST, COM)

Commands needing env var interpolation

API Reference

ShellAdapter

Subprocess execution with alias and environment variable expansion.

class buvis.pybase.adapters.ShellAdapter(*, suppress_logging: bool = False)

Bases: object

A class for executing shell commands and logging their output, with support for command aliases and environment variables.

It executes shell commands, logs the output, and handles errors.

alias(alias: str, command: str) None

Define a command alias.

Parameters:
  • alias – The alias name to define.

  • command – The command string that the alias maps to.

exe(command: str, working_dir: Path | None) tuple[str, str]

Execute a shell command and log its output.

This method runs the given command, logs stdout as info and stderr as error. If the command fails, it logs both stdout and stderr.

Parameters:
  • command – A string representing the command to execute.

  • working_dir – Path to directory where the command will be executed.

Returns:

A tuple of (stderr, stdout) from command execution. On failure, returns (error_message, empty string).

interact(command: str, prompt: str, working_dir: Path | None) None
is_command_available(command: str) bool

Check if a given command is available in the system PATH.

Parameters:

command – The name of the command to check.

Returns:

True if the command exists, False otherwise.

ConsoleAdapter

Rich console output wrapper for styled terminal messages.

class buvis.pybase.adapters.console.console.ConsoleAdapter

Bases: object

Rich console wrapper for styled terminal output.

Example

>>> from buvis.pybase.adapters import ConsoleAdapter
>>> console = ConsoleAdapter()
>>> console.success("Operation completed")

Checkmark, warning, and failure flows reuse the CHECKMARK, WARNING, and CROSSMARK markers together with STYLE_SUCCESS_MSG, STYLE_WARNING_MSG, and STYLE_FAILURE_MSG so decorated messages remain consistent.

capture() Capture

Return a Rich Capture context manager for console output.

Returns:

Context manager that captures console output.

Return type:

Capture

confirm(question: str) bool

Prompt the user for confirmation via Rich Confirm.ask.

Parameters:

question (str) – Prompt text presented to the user.

Returns:

Result of the confirmation.

Return type:

bool

failure(message: str, details: str | None = None) None

Print a red failure message, optionally including details.

Parameters:
  • message (str) – Text to display.

  • details (str | None) – Additional information to display after the failure message.

format_failure(message: str, details: str | None = None) str

Return a red crossmark Rich markup string with optional details.

Parameters:
  • message (str) – Text to format.

  • details (str | None) – Additional detail text to append.

Returns:

Rich markup string with crossmark and red styling.

Return type:

str

format_success(message: str) str

Return a green checkmark Rich markup string.

Parameters:

message (str) – Text to format.

Returns:

Rich markup string with checkmark and green styling.

Return type:

str

format_warning(message: str) str

Return an orange warning symbol Rich markup string.

Parameters:

message (str) – Text to format.

Returns:

Rich markup string with warning symbol and orange styling.

Return type:

str

nl() None

Output an empty line to the console.

panic(message: str, details: str | None = None) None

Print a failure message and exit the program.

Parameters:
  • message (str) – Text to display.

  • details (str | None) – Additional information to display after the failure message.

Note

Terminates the program by calling sys.exit().

print(message: str, *, mode: str = 'normal') None

Render text through the console with optional rendering modes.

Parameters:
  • message (str) – Content to print.

  • mode (str) – Rendering mode, one of normal, raw, or markdown_with_frontmatter.

print_side_by_side(title_left: str, text_left: str, title_right: str, text_right: str, *, mode_left: str = 'normal', mode_right: str = 'normal') None

Render two panels in a half-width, side-by-side layout where each panel uses half the console width to keep columns balanced.

Parameters:
  • title_left (str) – Panel title for the left column.

  • text_left (str) – Content for the left column body.

  • title_right (str) – Panel title for the right column.

  • text_right (str) – Content for the right column body.

  • mode_left (str) – Rendering mode for the left column content.

  • mode_right (str) – Rendering mode for the right column content.

Example

>>> console.print_side_by_side(
...     title_left="Config",
...     text_left="name: buvis\n---\n# details",
...     title_right="Result",
...     text_right="Success",
...     mode_left="markdown_with_frontmatter",
...     mode_right="raw",
... )
status(message: str) Status

Return a Rich Status context manager with arrow3 spinner.

Parameters:

message (str) – Text to display while the status context is active.

Returns:

Context manager for long-running operations.

Return type:

Status

success(message: str) None

Print a green checkmark status message.

Parameters:

message (str) – Text to display.

warning(message: str) None

Print an orange warning status message.

Parameters:

message (str) – Text to display.

console singleton instance exposes the adapter for quick access.

buvis.pybase.adapters.logging_to_console(*, show_level: bool = True, show_time: bool = False, show_path: bool = False) Generator[None, None, None]

Context manager that routes logging output through the Rich console.

Temporarily adds a CapturingRichHandler to the root logger, setting the logger level to INFO while the context is active and restoring the previous handler and level on exit.

Parameters:
  • show_level (bool) – Include the log level in the captured output. Defaults to True.

  • show_time (bool) – Include timestamps in the captured output. Defaults to False.

  • show_path (bool) – Include the logger path in the captured output. Defaults to False.

Yields:

None – Logging is redirected to the Rich console for the duration of the context.

Example

>>> with logging_to_console():
...     logging.info("Hello Rich")

UvAdapter

Fast Python package manager integration with auto-installation.

class buvis.pybase.adapters.UvAdapter

Bases: object

uv package manager integration.

Provides auto-installation of uv and PATH configuration for cross-platform Python tooling.

static ensure_uv() None

Ensure uv is installed and available in PATH.

If uv is not found, automatically installs it using: - Windows: PowerShell installer from astral.sh - Unix: curl installer from astral.sh

Updates PATH to include common uv installation directories: - ~/.cargo/bin - ~/.local/bin (Unix) - ~/AppData/Local/uv (Windows)

Exits with code 1 if installation fails.

UvToolManager

Manage and run CLI tools installed via uv.

class buvis.pybase.adapters.UvToolManager

Bases: object

Manage and run Python tools via uv.

Supports development workflows with automatic tool discovery and installation from local project sources.

static install_all(scripts_root: Path | None = None) None

Install all projects in src/ as uv tools.

Scans scripts_root/src/ for directories containing pyproject.toml and installs each as a uv tool.

Parameters:

scripts_root – Root directory containing src/. Defaults to cwd.

static install_tool(project_path: Path) None

Install a project as a uv tool.

Uses –force –upgrade to ensure latest version. On failure, cleans the tool cache and retries once.

Parameters:

project_path – Directory containing pyproject.toml.

classmethod run(script_path: str, args: list[str] | None = None) None

Run a tool from local venv, project source, or installed uv tool.

Execution priority when BUVIS_DEV_MODE=1:
  1. Local .venv/bin/{tool} if exists

  2. uv run from project source (src/{pkg}/pyproject.toml)

  3. Exit with error if neither found

Execution priority when BUVIS_DEV_MODE unset:
  1. uv tool run {tool}

  2. Auto-install from local source if tool not found

  3. Exit with error if no source available

Parameters:
  • script_path – Path to the launcher script (used to derive tool name).

  • args – Command arguments. Defaults to sys.argv[1:].

Note

Tool name derived from script stem: my-tool -> pkg my_tool.

JiraAdapter

JIRA REST API adapter for issue creation.

class buvis.pybase.adapters.JiraAdapter(settings: JiraSettings)

Bases: object

JIRA REST API adapter for issue operations.

Requirements:

Provide a populated JiraSettings instance with server and token.

Optional:

Configure proxy in the settings to route requests through a proxy server.

Example

>>> settings = JiraSettings(server="https://jira", token="abc123")
>>> jira = JiraAdapter(settings)
>>> issue = JiraIssueDTO(...)
>>> created = jira.create(issue)
>>> print(created.link)
>>> jira.get(created.id)
>>> results = jira.search("project = PROJ ORDER BY created DESC", max_results=5)
>>> jira.transition(created.id, "Start Progress")
add_comment(issue_key: str, body: str, is_internal: bool = False) JiraCommentDTO

Add comment to issue.

Parameters:
  • issue_key – JIRA issue key.

  • body – Comment text content.

  • is_internal – If True, restrict visibility to Administrators.

Returns:

JiraCommentDTO for the created comment.

Raises:

JiraNotFoundError – Issue does not exist.

create(issue: JiraIssueDTO) JiraIssueDTO

Create a JIRA issue via the REST API.

Parameters:

issue (JiraIssueDTO) – containing all required fields.

Returns:

populated with server-assigned id and link.

Return type:

JiraIssueDTO

Custom Field Mappings:

Determined by self._settings.field_mappings. Defaults: ticket -> customfield_11502, team -> customfield_10501, feature -> customfield_10001, region -> customfield_12900.

Note

Custom fields customfield_10001 (feature) and customfield_12900 (region) require post-creation update due to JIRA API limitations.

get(issue_key: str) JiraIssueDTO

Retrieve issue by key.

Parameters:

issue_key – JIRA issue key (e.g., “PROJ-123”).

Returns:

JiraIssueDTO with issue data.

Raises:

JiraNotFoundError – Issue does not exist.

get_comments(issue_key: str) list[JiraCommentDTO]

Get all comments on issue.

Parameters:

issue_key – JIRA issue key.

Returns:

List of JiraCommentDTO, chronologically ordered.

Raises:

JiraNotFoundError – Issue does not exist.

Get available issue link types.

Returns:

List of link type names (e.g., “Blocks”, “Duplicates”).

Raises:

JIRAError – JIRA API call failed.

get_transitions(issue_key: str) list[dict[str, str]]

List available transitions for issue.

Parameters:

issue_key – JIRA issue key.

Returns:

List of dicts with “id” and “name” keys.

Raises:

JiraNotFoundError – Issue does not exist.

Create link between issues.

Parameters:
  • from_key – Source issue key (outward side of link).

  • to_key – Target issue key (inward side of link).

  • link_type – Link type name (e.g., “Blocks”, “Duplicates”).

Raises:
  • JiraNotFoundError – Either issue does not exist.

  • JiraLinkError – Link creation failed.

search(jql: str, start_at: int = 0, max_results: int = 50, fields: str | None = None) JiraSearchResult

Execute JQL query with pagination.

Parameters:
  • jql – JQL query string.

  • start_at – Index of first result (for pagination).

  • max_results – Maximum results to return.

  • fields – Comma-separated field names to include, or None for all.

Returns:

JiraSearchResult with matching issues and pagination info.

transition(issue_key: str, transition: str, fields: dict[str, Any] | None = None, comment: str | None = None) None

Execute workflow transition.

Parameters:
  • issue_key – JIRA issue key.

  • transition – Transition ID or name.

  • fields – Optional field updates during transition.

  • comment – Optional comment to add during transition.

Raises:
  • JiraNotFoundError – Issue does not exist.

  • JiraTransitionError – Transition unavailable.

update(issue_key: str, fields: dict[str, Any]) JiraIssueDTO

Update issue fields.

Parameters:
  • issue_key – Issue to update.

  • fields – Dict of field names to new values.

Returns:

Updated JiraIssueDTO.

Raises:

JiraNotFoundError – Issue does not exist.

class buvis.pybase.adapters.jira.domain.JiraIssueDTO(project: str, title: str, description: str, issue_type: str, labels: list[str], priority: str, ticket: str, feature: str, assignee: str, reporter: str, team: str | None, region: str | None, id: str | None = None, link: str | None = None)

Bases: object

DTO for JIRA issue creation.

Maps Python fields to JIRA API field names. Custom field IDs are configured via JiraSettings.field_mappings.

project

JIRA project key (e.g., ‘BUV’).

Type:

str

title

Issue summary text, maps to JIRA ‘summary’.

Type:

str

description

Issue body text.

Type:

str

issue_type

Type name (e.g., ‘Task’, ‘Bug’).

Type:

str

labels

List of label strings.

Type:

list[str]

priority

Priority name (e.g., ‘Medium’, ‘High’).

Type:

str

ticket

Parent ticket reference (custom field).

Type:

str

feature

Feature/epic link (custom field).

Type:

str

assignee

Assignee username key.

Type:

str

reporter

Reporter username key.

Type:

str

team

Team name (custom field), None if not set.

Type:

str | None

region

Region value (custom field), None if not set.

Type:

str | None

id

Server-assigned issue key (e.g., ‘BUV-123’), populated after creation.

Type:

str | None

Permalink URL, populated after creation.

Type:

str | None

Platform-Specific Adapters

OutlookLocalAdapter

Windows-only adapter for local Outlook COM automation. Requires pywin32 and a local Outlook installation. Only available when os.name == "nt".

Note

This adapter cannot be autodocumented cross-platform as it raises OSError on import when os.name != "nt".

Methods:

  • __init__(): Connect to local Outlook via COM. Initializes MAPI namespace and default calendar.

  • create_timeblock(appointment_input: dict): Create a calendar appointment. Keys: subject, body, duration (minutes), location, categories, start (optional datetime).

  • get_all_appointments(): Retrieve all calendar appointments sorted by start time.

  • get_day_appointments(appointments, date): Filter appointments to a single day.

  • get_conflicting_appointment(desired_start, desired_duration, debug_level=0): Find appointment conflicting with proposed time slot.

Example:

# Windows only
from buvis.pybase.adapters import OutlookLocalAdapter

adapter = OutlookLocalAdapter()
adapter.create_timeblock({
    "subject": "Check-in",
    "body": "Daily sync",
    "duration": 30,
    "location": "Desk",
    "categories": "Work"
})

Examples

ShellAdapter Example

from pathlib import Path

from buvis.pybase.adapters import ShellAdapter

shell = ShellAdapter()
shell.alias("lint", "poetry run lint")
if not shell.is_command_available("git"):
    raise RuntimeError("git is required for this workflow")

stderr, stdout = shell.exe("lint", Path("src"))
if stderr:
    raise RuntimeError(f"lint failed: {stderr.strip()}")
print(stdout)

ConsoleAdapter Example

from buvis.pybase.adapters.console.console import console

with console.status("Deploying service"):
    console.print("Gathering assets")
    if not console.confirm("Continue deployment?"):
        console.failure("Deployment aborted by user")
        raise SystemExit(1)
console.success("Deployment finished")

JiraAdapter Example

Ensure configuration exposes server and token before instantiating the adapter.

from jira.exceptions import JIRAError
from buvis.pybase.adapters import JiraAdapter
from buvis.pybase.adapters.jira.domain import JiraIssueDTO

cfg = MyConfig()  # supplies server, token, and optional proxy
jira = JiraAdapter(cfg)
issue = JiraIssueDTO(
    project="BUV",
    title="Document new adapter patterns",
    description="Describe adapter usage in docs",
    issue_type="Task",
    labels=["docs", "pybase"],
    priority="Medium",
    ticket="BUV-123",
    feature="adapter-workflows",
    assignee="alice",
    reporter="bob",
    team="platform",
    region="emea",
)
try:
    created = jira.create(issue)
    print(f"Issue created: {created.id} -> {created.link}")
except JIRAError as e:
    print(f"JIRA API error: {e.status_code} - {e.text}")
    raise

UvAdapter Example

from buvis.pybase.adapters import UvAdapter

# Ensure uv is installed and PATH is configured
UvAdapter.ensure_uv()
# uv is now available for subprocess calls
# Use UvToolManager for running uv-managed tools

UvToolManager Example

from pathlib import Path

from buvis.pybase.adapters import UvToolManager

project_root = Path("/project")
# Directory layout:
# /project
# ├── bin/
# └── src/
#     └── my_tool/
UvToolManager.install_all(project_root)
UvToolManager.install_tool(project_root / "src" / "my_tool")
UvToolManager.run(project_root / "bin" / "my-tool", ["--help"])  # exits on completion