From 81ade7bf1c458b7dbae69e330b0dee32d5770577 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Mon, 24 Aug 2026 10:51:07 -0400 Subject: [PATCH] Streamline the managed config setup wizard --- pyproject.toml | 3 + src/ucode/cli.py | 44 ++-- src/ucode/managed_wizard.py | 190 ++++++-------- src/ucode/ui.py | 464 +++++++++++++++++++++++++++++++---- tests/test_cli.py | 27 ++ tests/test_managed_wizard.py | 108 +++++++- tests/test_ui.py | 226 +++++++++++++++++ uv.lock | 336 ++++++++++++++----------- 8 files changed, 1077 insertions(+), 321 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 27eb0619..79f53553 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,9 @@ dependencies = [ # transitively by mcp itself); `ucode.mcp_proxy` selects whichever the # installed SDK uses at runtime, so no direct httpx2 dependency is declared. "httpx>=0.27.1", + # Polished searchable/selectable prompts for the managed-config setup wizard. Questionary remains + # in use for the specialized MCP pickers, whose custom viewport and toolbar depend on its API. + "InquirerPy>=0.3.4", # `ucode mcp-proxy` bridges a client's stdio MCP transport to a Databricks # streamable-HTTP MCP endpoint, injecting a freshly-minted OAuth bearer per # request, via the SDK's stdio server + streamable-HTTP client. Works against diff --git a/src/ucode/cli.py b/src/ucode/cli.py index d2468457..4dd74df1 100644 --- a/src/ucode/cli.py +++ b/src/ucode/cli.py @@ -279,9 +279,9 @@ def _maybe_offer_admin_setup(workspace: str, profile: str | None) -> None: if not is_admin: return print_note( - "✨ New: run `ucode setup` to publish a managed config to a workspace — set agents, models, mcps " - "and skills once, and every developer inherits them when running `ucode`. This scales " - "delivery of coding agents to all developers without each one setting up ucode themselves." + "✨ New: run `ucode setup` to publish a managed config to a workspace — set agents, models, " + "MCP servers, and skills once, and every developer inherits them when running `ucode`. " + "This scales delivery of coding agents without each developer setting up ucode themselves." ) @@ -307,14 +307,19 @@ def _print_discovery_diagnostics(state: dict) -> None: print_note("Re-run with `UCODE_DEBUG=1` to log raw discovery responses to ~/.ucode/debug.log.") -def _prompt_for_configuration(tool: str | None = None) -> tuple[str, str | None]: +def _prompt_for_configuration( + tool: str | None = None, + *, + show_section: bool = True, + prompt: str = "Select workspace:", +) -> tuple[str, str | None]: if tool is None: desc = "Configure your Databricks workspace" else: desc = f"Configure {TOOL_SPECS[tool]['display']} to use your Databricks endpoint." with spinner("Loading Databricks workspaces and profiles..."): profiles = get_databricks_profiles() - return prompt_for_workspace(desc, profiles) + return prompt_for_workspace(desc, profiles, show_section=show_section, prompt=prompt) def _parse_agents_option(agents: str) -> list[str]: @@ -425,6 +430,7 @@ def configure_shared_state( use_pat: bool | None = None, skip_model_discovery: bool = False, skip_preflight: bool = False, + connection_checks_verified: bool = False, fable_enabled: bool | None = None, databricks_ai_tools_enabled: bool | None = None, ) -> dict: @@ -446,6 +452,9 @@ def configure_shared_state( in ``_launch_tool``) and the gateway was verified by that earlier configure. Only the local profile resolution and the shared state assembly still run; the saved model lists are preserved. + ``connection_checks_verified`` skips only the auth and AI Gateway probes while still running + model discovery. This is for callers such as ``ucode setup`` that perform those checks earlier + in their own ordered UI; it prevents duplicate network calls and success messages. ``fable_enabled`` opts the premium Claude Fable family into Claude Code's ``ANTHROPIC_DEFAULT_FABLE_MODEL`` pin (default off). ``None`` means "inherit": a launch re-run keeps whatever the workspace was configured with; ``True``/ @@ -534,21 +543,27 @@ def configure_shared_state( # empty one as absent, so it never shadows the PAT. Pass the validated # token to avoid re-reading ~/.databrickscfg. ensure_pat_bearer(profile, pat) - ensure_databricks_auth(workspace, profile) - elif force_login: - run_databricks_login(workspace, profile) - else: - ensure_databricks_auth(workspace, profile) + if not connection_checks_verified: + ensure_databricks_auth(workspace, profile) + elif not connection_checks_verified: + if force_login: + run_databricks_login(workspace, profile) + else: + ensure_databricks_auth(workspace, profile) # After login the profile exists in ~/.databrickscfg, so a host->profile # lookup is reliable even when it returned nothing above. if profile is None: profile = find_profile_name_for_host(workspace) if profile: state["profile"] = profile - with spinner("Verifying Unity AI Gateway..."): - token = get_databricks_token(workspace, profile) - ensure_ai_gateway(workspace, token) - print_success("Unity AI Gateway detected") + # Discovery always needs a bearer token. ``connection_checks_verified`` means the caller + # already performed the visible auth and gateway probes; it does not mean this function can + # skip obtaining the token used by the model-list APIs below. + token = get_databricks_token(workspace, profile) + if not connection_checks_verified: + with spinner("Verifying Unity AI Gateway..."): + ensure_ai_gateway(workspace, token) + print_success("Unity AI Gateway detected") want_claude = ( fetch_all or "claude" in tools or "opencode" in tools or "copilot" in tools or "pi" in tools @@ -917,7 +932,6 @@ def status() -> int: profile = state.get("profile") if profile: print_kv("CLI profile", profile) - print_heading("Coding Agents") for tool, spec in TOOL_SPECS.items(): configured = tool in configured_tools diff --git a/src/ucode/managed_wizard.py b/src/ucode/managed_wizard.py index bd3199f9..c5c72db4 100644 --- a/src/ucode/managed_wizard.py +++ b/src/ucode/managed_wizard.py @@ -3,7 +3,7 @@ Workspace admins run this to build the ``CodingAgentConfig`` their developers will pull, then publish it with ``ucode apply`` (a separate command, so the manifest can be reviewed or tested first). The editable draft lives at ``~/.ucode/managed-state.json`` and is never overwritten by ordinary -launches. +workspace-managed launches. Authoring is split across commands so an admin can change one part without walking the whole flow: ``ucode setup`` picks the agents and models, and ``ucode setup mcps`` / ``skills`` / ``spend-tiers`` @@ -32,6 +32,7 @@ create_coding_agent_config, delete_coding_agent_config, discover_claude_models_unbucketed, + ensure_ai_gateway, ensure_databricks_auth, get_databricks_token, has_cached_model_provider_services, @@ -64,6 +65,7 @@ from ucode.ui import ( console, format_usd, + inquirerpy_wizard, kv_line, print_err, print_heading, @@ -73,6 +75,9 @@ print_success, print_warning, print_warning_panel, + print_wizard_header, + print_wizard_outro, + print_wizard_step, prompt_for_multi_selection, prompt_for_percentage, prompt_for_selection, @@ -82,14 +87,6 @@ spinner, ) -# The OS-level managed settings file `use_as_global_settings` writes for each agent — named in the -# prompt so an admin sees exactly what answering "yes" touches. Yes writes this file (needs sudo -# once) so a bare `claude`/`codex` reaches the gateway on its own; no keeps it ucode-only. -GLOBAL_SETTINGS_FILES = { - "claude": "managed-settings.json", - "codex": "managed_config.toml", -} - BUDGET_POLICY_BLURB = ( "As the workspace spends more of a budget, a tiered spend policy automatically switches " "everyone's default agent and model to a cheaper one — for example Claude Code / Opus normally, " @@ -412,8 +409,8 @@ def _confirm_agent(tool: str, agent_config: dict) -> None: """One consistent closing line per agent in step 2, whatever its model shape. Every agent — a single-model codex, a multi-model opencode, a family-slotted claude — ends its - block with the same `✔ configured — · ` line, so the step reads as a - uniform checklist rather than each agent's picker trailing off differently. + block with the same `✔ configured — ` line, so the step reads as a uniform + checklist rather than each agent's picker trailing off differently. """ display = TOOL_SPECS.get(tool, {}).get("display", tool) model_config = agent_config.get("model_config") or {} @@ -421,26 +418,9 @@ def _confirm_agent(tool: str, agent_config: dict) -> None: provider = model_config.get("model_provider_service") if provider: detail = f"{detail} via {provider}" - if tool in GLOBAL_SETTINGS_AGENTS: - scope = "global settings" if agent_config.get("use_as_global_settings") else "ucode-only" - detail = f"{detail} · {scope}" print_success(f"{display} configured — {detail}") -def _render_family_slots(slots: dict[str, str]) -> None: - """Recap the Claude family → model slots just chosen, before the overall-default question. - - Same "form filling in" motif as :func:`_selected_recap`: the per-family answers scrolled by one at - a time, so gathering them into one box makes "which of these is the overall default?" a choice - over something the admin can see rather than recall. - """ - lines = [ - kv_line(slot.removeprefix("default_").removesuffix("_model"), model) - for slot, model in slots.items() - ] - print_panel("Claude Code models", lines) - - def _prompt_claude_models(state: dict) -> dict: """Build Claude's ``model_config`` one family slot at a time. @@ -463,10 +443,6 @@ def _prompt_claude_models(state: dict) -> dict: print_warning(f"No Claude models were discovered for {display} on this workspace.") return {"default_model": _require_text(f"Default model for {display}")} - print_note( - "Claude Code picks a model by family, so set a default per family. Skip any family you " - "don't want configured — it falls back to the overall default." - ) slots: dict[str, str] = {} custom: list[str] = [] for family in ANTHROPIC_FAMILIES: @@ -510,9 +486,8 @@ def _prompt_claude_models(state: dict) -> dict: model_config["default_model"] = chosen[0] print_note(f"Only one model configured, so it's {display}'s overall default.") else: - _render_family_slots(slots) model_config["default_model"] = _require_selection( - f"Which of those is {display}'s overall default?", [(m, m) for m in chosen] + "Overall Default Model", [(m, m) for m in chosen] ) if custom: model_config["custom_models"] = list(dict.fromkeys(custom)) @@ -598,10 +573,6 @@ def _prompt_claude_provider_family_models(targets: list[str], service_name: str) print_note(f"Quick setup — {summary} (default: {default_family}).") return model_config - print_note( - f"Claude Code picks a model by family, so set a default per family from {service_name}. " - "Skip any family you don't want configured — it falls back to the overall default." - ) slots: dict[str, str] = {} for family in ANTHROPIC_FAMILIES: family_targets = by_family.get(family) @@ -625,13 +596,11 @@ def _prompt_claude_provider_family_models(targets: list[str], service_name: str) model_config["default_model"] = chosen[0] print_note(f"Only one model configured, so it's {display}'s overall default.") else: - if slots: - _render_family_slots(slots) # Offered over every target, not just the slots: `default_model` needn't be a family model, # and a mixed-catalog service may expose one an admin wants as the overall default. options = chosen or list(targets) model_config["default_model"] = _require_selection( - f"Which of those is {display}'s overall default?", [(m, m) for m in options] + "Overall Default Model", [(m, m) for m in options] ) return model_config @@ -1077,8 +1046,10 @@ def _render_summary(workspace: str, manifest: dict) -> None: provider = model_config.get("model_provider_service") if provider: detail = f"{detail} via {provider}" - # Only agents that can use global settings carry the scope label; for the rest it's not a choice. - if tool in GLOBAL_SETTINGS_AGENTS: + # Older/imported manifests can still carry this field even though the interactive setup flow + # no longer authors it. Show an explicit legacy value without labelling every newly-authored + # agent "ucode-only" as though setup had asked for a scope. + if tool in GLOBAL_SETTINGS_AGENTS and "use_as_global_settings" in agent_config: scope = ( "global settings" if agent_config.get("use_as_global_settings") else "ucode-only" ) @@ -1124,7 +1095,7 @@ def _render_summary(workspace: str, manifest: dict) -> None: else: lines.append(kv_line("Tiered Spend Policy", "none")) - print_panel("Configuration summary", lines) + print_panel("Complete configuration", lines) def _config_facts(manifest: dict) -> list[tuple[str, str, str]]: @@ -1158,7 +1129,7 @@ def _config_facts(manifest: dict) -> list[tuple[str, str, str]]: facts.append((f"agent:{tool}:model:{family}", f"{display} ({family})", str(model))) elif isinstance(models, list) and len(models) > 1: facts.append((f"agent:{tool}:models", f"{display} models", ", ".join(map(str, models)))) - if tool in GLOBAL_SETTINGS_AGENTS: + if tool in GLOBAL_SETTINGS_AGENTS and "use_as_global_settings" in agent_config: scope = ( "global settings" if agent_config.get("use_as_global_settings") else "ucode-only" ) @@ -1240,7 +1211,7 @@ def _render_config_diff(existing: dict | None, incoming: dict, workspace: str) - return True -def _require_admin(workspace: str, token: str) -> None: +def _require_admin(workspace: str, token: str, *, quiet: bool = False) -> bool | None: """Stop unless the caller is a workspace admin. An unverifiable check (SCIM unreachable) warns and continues: the API enforces the same rule, so @@ -1254,12 +1225,14 @@ def _require_admin(workspace: str, token: str) -> None: "coding config, so it is restricted to workspace admins." ) if admin is None: - print_warning( - "Could not verify workspace admin permissions. Continuing — `ucode apply` will fail " - "if you lack them." - ) - else: + if not quiet: + print_warning( + "Could not verify workspace admin permissions. Continuing — `ucode apply` will " + "fail if you lack them." + ) + elif not quiet: print_success("Admin permissions verified") + return admin def _handle_existing_config(workspace: str, token: str) -> tuple[bool, dict | None]: @@ -1282,15 +1255,12 @@ def _handle_existing_config(workspace: str, token: str) -> tuple[bool, dict | No if existing is None: return True, None - print_warning( - "This workspace already has a managed configuration — one config covers every agent, MCP " - "server, skill, tracing table, and budget policy for the whole workspace." - ) + print_warning("This workspace already has a managed config.") choice = prompt_for_selection( "What would you like to do?", [ - ("create", "Author a new config (replaces the existing one when you publish)"), - ("delete", "Delete the existing config (removes it from the workspace, leaves none)"), + ("create", "Author a replacement (takes effect when published)"), + ("delete", "Delete it from the workspace"), ], ) if choice is None: @@ -1367,7 +1337,7 @@ def setup_from_file(path: str) -> int: save_managed_state(workspace, manifest) _render_summary(workspace, manifest) - print_success(f"Saved to {manifest_path.name} -> ~/.ucode/managed-state.json") + print_success(f"Configuration loaded from {manifest_path.name}") _print_next_steps(manifest) return 0 @@ -1392,30 +1362,17 @@ def _command_line(command: str, description: str, *, marker: str = " ", width: i # `ucode setup` walks these phases in order; the banners announce each one so the admin can see how # far along the flow they are, the way a multi-page form numbers its pages. -SETUP_STEP_TITLES = ["Coding agents", "Models & settings", "Default agent"] +SETUP_STEP_TITLES = [ + "Select the workspace", + "Select coding agents", + "Select models for each agent", + "Select the default agent", +] def _step_banner(index: int, title: str) -> None: - """Announce one phase of `ucode setup` as `step N of M`.""" - print_section(f"ucode setup · step {index} of {len(SETUP_STEP_TITLES)} · {title}") - - -def _selected_recap(workspace: str, enabled_agents: dict, default_agent: str | None) -> None: - """A compact panel of what's chosen so far, reprinted as the flow advances. - - Turns the run of prompts into something that reads like a form filling in: each phase reprints - the growing set of decisions before asking the next question. Agents still mid-configuration show - a `…` placeholder for their model. - """ - lines = [kv_line("Workspace", workspace)] - for tool, config in enabled_agents.items(): - model = (config.get("model_config") or {}).get("default_model") or "…" - lines.append(kv_line(TOOL_SPECS.get(tool, {}).get("display", tool), model)) - if default_agent: - lines.append( - kv_line("Default", TOOL_SPECS.get(default_agent, {}).get("display", default_agent)) - ) - print_panel("Selected so far", lines) + """Announce one phase of `ucode setup` on its vertical progress rail.""" + print_wizard_step(index, len(SETUP_STEP_TITLES), title) def _section_status_lines(manifest: dict, width: int = 0) -> list[str]: @@ -1528,6 +1485,7 @@ def _carry_forward_sections(previous: dict, manifest: dict) -> None: print_note(f"Rebuild it with `{rebuild}`.") +@inquirerpy_wizard def setup_command( from_file: str | None = None, *, @@ -1555,25 +1513,45 @@ def setup_command( # would be circular. from ucode.cli import _prompt_for_configuration, configure_shared_state - print_section("ucode setup") - print_note("Choose the coding agents and models for this workspace's managed config.") - print_note("Developers pull it automatically when they run ucode.") + print_wizard_header( + "ucode setup", + "Workspace-wide agent and model defaults · developers get updates automatically", + ) + _step_banner(1, SETUP_STEP_TITLES[0]) if workspace is None: - workspace, profile = _prompt_for_configuration() + workspace, profile = _prompt_for_configuration(show_section=False, prompt="Workspace:") + else: + print_note(f"Using {workspace}") # `configure_shared_state` below authenticates too and prints its own success line, so this one # stays quiet rather than reporting the same thing twice. It still has to run first: the admin # gate and the existing-config check both need a token before discovery. ensure_databricks_auth(workspace, profile, quiet=True) token = get_databricks_token(workspace, profile) - _require_admin(workspace, token) + admin_verified = _require_admin(workspace, token, quiet=True) + with spinner("Verifying Unity AI Gateway..."): + ensure_ai_gateway(workspace, token) + if admin_verified: + print_success("Workspace checks complete") + else: + print_warning( + "Workspace checks incomplete (2/3) — authentication · Unity AI Gateway; " + "permissions unverified" + ) + keep_going, published = _handle_existing_config(workspace, token) if not keep_going: + print_wizard_outro("Setup complete") return 0 # Discover the workspace's models and gateway URLs. This also logs in and persists local state. - state = configure_shared_state(workspace, profile=profile, force_login=False) + state = configure_shared_state( + workspace, + profile=profile, + force_login=False, + connection_checks_verified=True, + ) workspace = state.get("workspace") or workspace profile = state.get("profile") or profile @@ -1595,47 +1573,38 @@ def setup_command( previously_enabled = [ tool for tool in (previous.get("enabled_agents") or {}) if tool in available ] - _step_banner(1, SETUP_STEP_TITLES[0]) + _step_banner(2, SETUP_STEP_TITLES[1]) picked = prompt_for_tools( [(tool, TOOL_SPECS[tool]["display"]) for tool in available], preselected=previously_enabled or None, + prompt="Coding agents:", ) if not picked: print_note("No coding agents selected — nothing to configure.") + print_wizard_outro("No changes") return 0 - _step_banner(2, SETUP_STEP_TITLES[1]) + _step_banner(3, SETUP_STEP_TITLES[2]) enabled_agents: dict[str, dict] = {} - for index, tool in enumerate(picked, start=1): - print_heading(f"{TOOL_SPECS[tool]['display']} ({index} of {len(picked)})") + for tool in picked: + print_heading(f"Configure {TOOL_SPECS[tool]['display']}") provider_service = _select_provider_service(tool, workspace, token) # Always set: `_prompt_models_for_agent` re-prompts rather than returning empty, so every # enabled agent carries a default_model and any of them can be the default_agent. agent_config: dict = { "model_config": _prompt_models_for_agent(tool, state, provider_service) } - # Only claude and codex have an OS-level managed settings file that a bare `claude`/`codex` - # reads (`/etc/claude-code/managed-settings.json`, `/etc/codex/managed_config.toml`); the - # other agents don't, so we don't offer them the choice. - if tool in GLOBAL_SETTINGS_AGENTS: - binary = TOOL_SPECS[tool]["binary"] - agent_config["use_as_global_settings"] = prompt_yes_no_default( - f"Route `{binary}` through the gateway too, not just `ucode {binary}`? " - f"(writes {GLOBAL_SETTINGS_FILES[tool]}, needs sudo once)", - default=False, - ) enabled_agents[tool] = agent_config _confirm_agent(tool, agent_config) # Pick the default after configuring each agent, not before: by now the admin has seen every # agent's models go by, so "which is the default?" is a choice among things they've just set up - # rather than a bare list up front. The recap reprints those picks so the choice is informed. - _step_banner(3, SETUP_STEP_TITLES[2]) + # rather than a bare list up front. + _step_banner(4, SETUP_STEP_TITLES[3]) default_agent = picked[0] if len(picked) > 1: - _selected_recap(workspace, enabled_agents, default_agent=None) chosen = prompt_for_selection( - "Which coding agent should be the default?", + "Default agent:", [(tool, TOOL_SPECS[tool]["display"]) for tool in picked], ) if not chosen: @@ -1658,12 +1627,13 @@ def setup_command( print_err("The generated config is not valid:") for error in errors: print_note(error) + print_wizard_outro("Configuration could not be created", kind="error") return 1 save_managed_state(workspace, manifest) + print_wizard_outro("Setup complete") + print_success("Configuration saved") _render_summary(workspace, manifest) - console.print() - print_success("Saved to ~/.ucode/managed-state.json") _print_next_steps(manifest) _offer_apply() return 0 @@ -1731,7 +1701,7 @@ def _save_section_update(workspace: str, manifest: dict) -> int: save_managed_state(workspace, manifest) _render_summary(workspace, manifest) console.print() - print_success("Saved to ~/.ucode/managed-state.json") + print_success("Configuration saved") _print_next_steps(manifest) _offer_apply() return 0 @@ -1849,8 +1819,8 @@ def setup_help_command() -> int: """Walk through the whole managed-config setup, marking what this machine has authored. Hand-written rather than left to `--help`: the point is the *order* of the commands and the fact - that nothing reaches developers until `ucode apply`, neither of which a flag listing conveys. - Reads the manifest but never authenticates, so it works before `ucode configure`. + that nothing reaches developers until a workspace apply, neither of which a flag listing + conveys. Reads the manifest but never authenticates, so it works before `ucode configure`. """ print_section("ucode setup") print_note( @@ -1903,7 +1873,7 @@ def setup_help_command() -> int: ) ) print_note( - f"The draft lives in ~/.ucode/managed-state.json (workspace: {workspace or 'none'})." + f"The draft is saved at ~/.ucode/managed-state.json (workspace: {workspace or 'none'})." ) print_note( "Re-running `ucode setup` keeps the sections in step 2; to drop one, edit the draft and " @@ -2023,7 +1993,7 @@ def apply_command(*, yes: bool = False) -> int: print_err("The authored config is not valid, so it was not published:") for error in errors: print_note(error) - print_note("Re-run `ucode setup` to fix it, or edit ~/.ucode/managed-state.json.") + print_note("Re-run `ucode setup` to fix it, or edit the local managed configuration.") return 1 token = get_databricks_token(workspace, profile) diff --git a/src/ucode/ui.py b/src/ucode/ui.py index 47c85b2d..12437e87 100644 --- a/src/ucode/ui.py +++ b/src/ucode/ui.py @@ -9,10 +9,18 @@ import time from collections.abc import Callable, Iterator from contextlib import contextmanager +from contextvars import ContextVar from datetime import timedelta from decimal import ROUND_HALF_UP, Decimal +from functools import wraps +from types import MethodType +from typing import Any, TypedDict import questionary +from InquirerPy import inquirer +from InquirerPy.base.control import Choice as InquirerChoice +from InquirerPy.separator import Separator as InquirerSeparator +from InquirerPy.utils import InquirerPyStyle, get_style from prompt_toolkit.layout.containers import Window from prompt_toolkit.layout.dimension import Dimension from questionary.prompts.common import InquirerControl @@ -24,6 +32,136 @@ console = Console(highlight=False) err_console = Console(stderr=True, highlight=False) +# `ucode setup` opts into InquirerPy while the rest of the CLI stays on Questionary. Context-local +# state keeps nested helpers on the same backend without threading a presentation flag through every +# model/provider/budget function, and won't bleed between concurrent callers or tests. +_prompt_backend: ContextVar[str] = ContextVar("ucode_prompt_backend", default="questionary") +_wizard_rail_open: ContextVar[bool] = ContextVar("ucode_wizard_rail_open", default=False) + + +def _using_inquirerpy() -> bool: + return _prompt_backend.get() == "inquirerpy" + + +def _rail_prefix() -> str: + return "[dim]│[/dim] " if _wizard_rail_open.get() else "" + + +def _print_blank(*, stderr: bool = False) -> None: + target = err_console if stderr else console + target.print("[dim]│[/dim]" if _wizard_rail_open.get() else "") + + +def inquirerpy_wizard[**P, R](func: Callable[P, R]) -> Callable[P, R]: + """Run one wizard on InquirerPy and reset all terminal-layout state afterwards.""" + + @wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: + backend_token = _prompt_backend.set("inquirerpy") + rail_token = _wizard_rail_open.set(False) + try: + return func(*args, **kwargs) + except KeyboardInterrupt: + if _wizard_rail_open.get(): + print_wizard_outro("Setup interrupted", kind="warning") + raise + except Exception: + if _wizard_rail_open.get(): + print_wizard_outro("Setup stopped", kind="error") + raise + finally: + _wizard_rail_open.reset(rail_token) + _prompt_backend.reset(backend_token) + + return wrapped + + +_INQUIRER_STYLE = get_style( + { + "questionmark": "#666666", + "answermark": "#666666", + "answer": "#00d7d7 bold", + "input": "#00d7d7", + "question": "bold", + "answered_question": "bold", + "instruction": "#666666", + "long_instruction": "#666666", + "pointer": "#00d7d7 bold", + "checkbox": "#00d7d7", + "separator": "#8a8a8a bold", + "marker": "#00d7d7", + "fuzzy_prompt": "#00d7d7", + "fuzzy_info": "#666666", + "fuzzy_match": "#00d7d7 bold", + }, + style_override=False, +) + + +def _inquirer_marks() -> tuple[str, str]: + # InquirerPy inserts one space after the mark. Include one more while the wizard rail is open so + # question text aligns with Rich output's ``│ `` gutter. + return ("│ ", "│ ") if _wizard_rail_open.get() else ("◇", "◆") + + +class _InquirerCommon(TypedDict): + style: InquirerPyStyle + qmark: str + amark: str + raise_keyboard_interrupt: bool + + +def _inquirer_common() -> _InquirerCommon: + qmark, amark = _inquirer_marks() + return { + "style": _INQUIRER_STYLE, + "qmark": qmark, + "amark": amark, + "raise_keyboard_interrupt": True, + } + + +def _with_inquirer_rail(question: Any) -> Any: + """Prefix every row in an InquirerPy choice list with the wizard's live rail. + + InquirerPy applies ``qmark`` only to the question. Its option window starts back at column zero, + which both breaks the vertical rail and makes the highlighted row look less indented than status + output. Wrapping the control's two row formatters keeps highlighted, normal, and separator rows + under the same ``│ `` gutter without changing their values or key handling. + + Test doubles and non-list prompts have no ``content_control`` and pass through unchanged. + """ + if not _wizard_rail_open.get(): + return question + control = getattr(question, "content_control", None) + if control is None: + return question + + original_hover = control._get_hover_text + original_normal = control._get_normal_text + + def hover(_: Any, choice: Any) -> list[tuple[str, str]]: + return [("class:questionmark", "│ "), *original_hover(choice)] + + def normal(_: Any, choice: Any) -> list[tuple[str, str]]: + return [("class:questionmark", "│ "), *original_normal(choice)] + + control._get_hover_text = MethodType(hover, control) + control._get_normal_text = MethodType(normal, control) + return question + + +def _inquirer_filter_prompt() -> str: + return "│ Filter" if _wizard_rail_open.get() else "Filter" + + +def _choice_summary(values: list[str], labels: dict[str, str]) -> str: + selected = [labels.get(str(value), str(value)) for value in values] + if len(selected) <= 3: + return ", ".join(selected) + return f"{len(selected)} selected" + + # Past this many options the choice list is pinned to a fixed-height scrolling viewport (see # `_cap_choice_viewport`) rather than growing to fill the terminal, and the pickers append a # "↑/↓ scroll" note to their instruction line. The value is both the boundary and the number of @@ -91,13 +229,53 @@ def print_section(title: str) -> None: console.print(Panel(title, style="bold blue", expand=False)) -def print_heading(text: str) -> None: +def print_wizard_header(command: str, description: str) -> None: + """Open an interactive flow with compact command branding and a continuous rail. + + A wizard is one continuous composition, so a panel around its name makes the following steps + look like separate cards. Styling the command itself gives the flow an identity while leaving + the rest of the terminal as one canvas. + """ + head, separator, tail = command.partition(" ") + rendered = f"[bold cyan]{escape(head)}[/bold cyan]" + if separator: + rendered += f" [bold]{escape(tail)}[/bold]" console.print() - console.print(f"[bold]{text}[/bold]") + console.print(f"[bold cyan]┌[/bold cyan] {rendered}") + console.print(f"[dim]│[/dim] [dim]{escape(description)}[/dim]") + _wizard_rail_open.set(True) + + +def print_wizard_step(index: int, total: int, title: str) -> None: + """Render one node in a compact vertical stepper. + + The connector is printed immediately before every node after the first. Output and prompts from + the previous phase remain between the two nodes, so the rail visually joins the whole flow + without requiring a full-screen TUI or redrawing terminal history. + """ + console.print("[dim]│[/dim]") + console.print( + f"[bold cyan]◇[/bold cyan] [dim]{index}/{total}[/dim] [bold]{escape(title)}[/bold]" + ) + + +def print_wizard_outro(message: str, *, kind: str = "success") -> None: + """Close the active wizard rail and return subsequent output to the normal left margin.""" + if not _wizard_rail_open.get(): + return + color = {"success": "green", "warning": "yellow", "error": "red"}.get(kind, "cyan") + console.print("[dim]│[/dim]") + console.print(f"[bold {color}]└[/bold {color}] [bold]{escape(message)}[/bold]") + _wizard_rail_open.set(False) + + +def print_heading(text: str) -> None: + _print_blank() + console.print(f"{_rail_prefix()}[bold]{text}[/bold]") def print_kv(key: str, val: str) -> None: - console.print(f" [bold]{key}:[/bold] [cyan]{val}[/cyan]") + console.print(f"{_rail_prefix()}[bold]{key}:[/bold] [cyan]{val}[/cyan]") def kv_line(key: str, val: str) -> str: @@ -118,6 +296,11 @@ def print_panel(title: str, lines: list[str]) -> None: should be read as one unit (a config summary an admin is about to publish) reads as one, rather than as loose lines that blend into whatever the flow printed before it. """ + if _wizard_rail_open.get(): + print_heading(title) + for line in lines: + console.print(f"{_rail_prefix()}{line}") + return console.print() console.print(Panel("\n".join(lines), title=title, style="blue", expand=False)) @@ -129,6 +312,10 @@ def print_warning_panel(message: str, *, title: str = "Warning") -> None: dead-end message (e.g. "no budgets exist, nothing to do") the weight to be read before the flow exits, rather than scrolling past as one more line. """ + if _wizard_rail_open.get(): + print_heading(title) + console.print(f"{_rail_prefix()}[bold yellow]![/bold yellow] {message}") + return console.print() console.print( Panel(f"[bold yellow]![/bold yellow] {message}", title=title, style="yellow", expand=False) @@ -136,24 +323,24 @@ def print_warning_panel(message: str, *, title: str = "Warning") -> None: def print_note(text: str) -> None: - console.print(f"[dim]•[/dim] {text}") + console.print(f"{_rail_prefix()}[dim]•[/dim] {text}") def print_success(message: str) -> None: - console.print(f"[bold green]✔[/bold green] {message}") + console.print(f"{_rail_prefix()}[bold green]✔[/bold green] {message}") def print_warning(message: str) -> None: - console.print(f"[bold yellow]![/bold yellow] {message}") + console.print(f"{_rail_prefix()}[bold yellow]![/bold yellow] {message}") def print_warning_err(message: str) -> None: """``print_warning`` on stderr, for when stdout is a machine-read stream.""" - err_console.print(f"[bold yellow]![/bold yellow] {message}") + err_console.print(f"{_rail_prefix()}[bold yellow]![/bold yellow] {message}") def print_err(message: str) -> None: - err_console.print(f"[bold red]ERROR[/bold red] {message}") + err_console.print(f"{_rail_prefix()}[bold red]ERROR[/bold red] {message}") def heading(text: str) -> str: @@ -195,6 +382,9 @@ def current_message() -> str: current_message = message stop_event = threading.Event() + # Context variables don't propagate into this animation thread, so capture the wizard gutter + # now; otherwise the spinner is the one line that jumps outside an otherwise continuous rail. + spinner_prefix = "\033[2m│\033[0m " if _wizard_rail_open.get() else "" def spin() -> None: for frame in itertools.cycle("|/-\\"): @@ -202,7 +392,7 @@ def spin() -> None: break # `\033[K` erases to end of line so a shrinking dynamic message # doesn't leave stale characters behind. - sys.stdout.write(f"\r\033[2m{frame}\033[0m {current_message()}\033[K") + sys.stdout.write(f"\r{spinner_prefix}\033[2m{frame}\033[0m {current_message()}\033[K") sys.stdout.flush() time.sleep(0.1) sys.stdout.write("\r\033[K") @@ -344,65 +534,109 @@ def normalize_workspace_url(workspace: str) -> str: def prompt_for_workspace( description: str, profiles: list[tuple[str, str]] | None = None, + *, + show_section: bool = True, + prompt: str = "Select workspace:", ) -> tuple[str, str | None]: """Ask the user for a workspace URL, offering profiles as quick-select. `profiles` is a list of (host_url, profile_name) tuples. Caller fetches them — `ui.py` stays Databricks-agnostic. Duplicate hosts (multiple profiles pointing at the same workspace) are shown separately; the picker - returns the exact (host, profile_name) the user selected. Returns - ``(url, profile_name)``; profile_name is ``None`` when the user typed a - URL manually. + returns the exact (host, profile_name) the user selected. ``show_section=False`` lets a larger + wizard supply its own step banner. Returns ``(url, profile_name)``; profile_name is ``None`` + when the user typed a URL manually. """ - console.print() - console.print(Panel(description, title="ucode setup", style="bold blue", expand=False)) + # Keep section labels visually consistent with every other CLI phase: the label is the panel + # body, not a border title. Callers that already printed a larger flow's step banner can suppress + # this local section rather than showing two boxes back to back. + if show_section: + print_section(description) if profiles: name_header = "Profile Name" url_header = "Workspace URL" - # Clamp so a single very long profile name can't push the URL column - # off-screen on an 80-col terminal — questionary doesn't wrap row - # titles cleanly, and a wrapped row breaks the picker visually. + # Clamp so a single very long profile name can't push the URL column off-screen. max_name_width = 40 name_width = min( max_name_width, max(len(name_header), *(len(name) for _, name in profiles)), ) - # Match the 2-char cursor gutter so the header line aligns with rows. header_title = f" {name_header.ljust(name_width)} {url_header}" - choices: list[questionary.Choice | questionary.Separator] = [ - questionary.Separator(header_title) - ] - for host, profile_name in profiles: + + def profile_row(host: str, profile_name: str) -> str: display_name = ( profile_name if len(profile_name) <= name_width else profile_name[: name_width - 1] + "…" ) - row_title = f"{display_name.ljust(name_width)} {host}" - # Value carries the full untruncated profile name so downstream - # `--profile` calls always use the real name, not the display form. - choices.append(questionary.Choice(title=row_title, value=(host, profile_name))) - choices.append(questionary.Choice(title="Enter a different URL", value=None)) - style = questionary.Style( - [ - ("highlighted", "fg:cyan bold"), - ("pointer", "fg:cyan bold"), - ("answer", "fg:cyan"), - ("separator", "fg:white bold"), + return f"{display_name.ljust(name_width)} {host}" + + if _using_inquirerpy(): + inquirer_choices: list[InquirerChoice | InquirerSeparator] = [ + InquirerSeparator(header_title), + *[ + InquirerChoice(value=(host, profile_name), name=profile_row(host, profile_name)) + for host, profile_name in profiles + ], + InquirerChoice(value=None, name="Enter a different URL"), ] - ) - choice = questionary.select( - "Select workspace:", choices=choices, style=style, pointer="›", qmark="" - ).ask() + + def workspace_summary(answer: object) -> str: + # InquirerPy passes the selected row's display name to transformers, not its raw + # value. A tuple check therefore mislabeled every completed profile selection as + # "Enter a different URL". Preserve the actual profile-and-workspace display row. + return str(answer) + + question = inquirer.select( + message=prompt, + choices=inquirer_choices, + pointer="❯", + instruction="↑↓ move · enter select", + transformer=workspace_summary, + max_height=_SCROLL_HINT_THRESHOLD, + **_inquirer_common(), + ) + choice = _with_inquirer_rail(question).execute() + else: + choices: list[questionary.Choice | questionary.Separator] = [ + questionary.Separator(header_title), + *[ + questionary.Choice( + title=profile_row(host, profile_name), value=(host, profile_name) + ) + for host, profile_name in profiles + ], + questionary.Choice(title="Enter a different URL", value=None), + ] + style = questionary.Style( + [ + ("highlighted", "fg:cyan bold"), + ("pointer", "fg:cyan bold"), + ("answer", "fg:cyan"), + ("separator", "fg:white bold"), + ] + ) + choice = questionary.select( + prompt, choices=choices, style=style, pointer="›", qmark="" + ).ask() if isinstance(choice, tuple): host, profile_name = choice return normalize_workspace_url(host), profile_name while True: - raw_value = console.input(f" [bold]Workspace URL[/bold] {muted('›')} ").strip() + if _using_inquirerpy(): + raw_value = inquirer.text( + message=prompt, + validate=lambda value: bool(str(value).strip()), + invalid_message="Enter a workspace URL", + mandatory=True, + **_inquirer_common(), + ).execute() + else: + raw_value = console.input(f" [bold]Workspace URL[/bold] {muted('›')} ").strip() try: - return normalize_workspace_url(raw_value), None + return normalize_workspace_url(str(raw_value)), None except ValueError as exc: print_err(str(exc)) @@ -420,6 +654,32 @@ def prompt_for_tools( agents an existing managed config already enables). Returns [] if the user submits an empty selection. """ + preselected_set = {str(item) for item in preselected} if preselected is not None else None + if _using_inquirerpy(): + labels = dict(available) + choices = [ + InquirerChoice( + value=tool_id, + name=display, + enabled=(preselected_set is None or tool_id in preselected_set), + ) + for tool_id, display in available + ] + question = inquirer.checkbox( + message=prompt, + choices=choices, + pointer="❯", + enabled_symbol="◉", + disabled_symbol="○", + instruction="↑↓ move · space toggle · enter confirm", + transformer=lambda values: _choice_summary(values, labels), + max_height=_SCROLL_HINT_THRESHOLD, + mandatory=False, + **_inquirer_common(), + ) + answer = _with_inquirer_rail(question).execute() + return list(answer or []) + style = questionary.Style( [ # Theme-agnostic picker: every row renders in the terminal's @@ -432,7 +692,6 @@ def prompt_for_tools( ("answer", "fg:cyan"), ] ) - preselected_set = {str(item) for item in preselected} if preselected is not None else None choices = [ questionary.Choice( title=display, @@ -470,6 +729,52 @@ def prompt_for_multi_selection( ``searchable`` lets the user narrow a long list by typing; see :func:`prompt_for_selection` for why it trades away j/k navigation. """ + preselected_set = {str(item) for item in preselected} if preselected is not None else set() + if _using_inquirerpy(): + labels = dict(options) + choices = [ + InquirerChoice( + value=value, + name=option_label, + enabled=value in preselected_set, + ) + for value, option_label in options + ] + # Fuzzy search owns a separate input row. On a short list that row looks like a blank first + # choice with a second cursor, so reserve it for lists long enough to benefit from filtering. + if searchable and len(options) > _SCROLL_HINT_THRESHOLD: + question = inquirer.fuzzy( + message=prompt, + choices=choices, + pointer="❯", + transformer=lambda values: _choice_summary(values, labels), + max_height=_SCROLL_HINT_THRESHOLD, + mandatory=False, + multiselect=True, + prompt=_inquirer_filter_prompt(), + marker="◉", + marker_pl="○", + info=False, + instruction="type filter · space toggle · enter confirm", + keybindings={"toggle": [{"key": "space"}]}, + **_inquirer_common(), + ) + else: + question = inquirer.checkbox( + message=prompt, + choices=choices, + pointer="❯", + transformer=lambda values: _choice_summary(values, labels), + max_height=_SCROLL_HINT_THRESHOLD, + mandatory=False, + enabled_symbol="◉", + disabled_symbol="○", + instruction="↑↓ move · space toggle · enter confirm", + **_inquirer_common(), + ) + answer = _with_inquirer_rail(question).execute() + return None if answer is None else list(answer) + style = questionary.Style( [ ("pointer", "fg:cyan bold"), @@ -478,7 +783,6 @@ def prompt_for_multi_selection( ("answer", "fg:cyan"), ] ) - preselected_set = {str(item) for item in preselected} if preselected is not None else set() choices = [ questionary.Choice(title=option_label, value=value, checked=value in preselected_set) for value, option_label in options @@ -524,6 +828,18 @@ def prompt_for_text( word-like default vanished from the prompt entirely — numeric ones like ``[80]`` are not valid tags and survived, which is why this looked fine wherever it was checked. """ + if _using_inquirerpy(): + answer = inquirer.text( + message=prompt, + default=default or "", + instruction="enter to accept current value" if default else "", + validate=lambda value: bool(str(value).strip()), + invalid_message="Enter a value", + mandatory=True, + **_inquirer_common(), + ).execute() + return str(answer).strip() if answer is not None else default + hint = f" {escape(f'[{default}]')} (enter to accept)" if default else "" while True: try: @@ -553,6 +869,28 @@ def prompt_for_percentage(prompt: str, *, default: float | None = None) -> float Raises ``KeyboardInterrupt`` on closed stdin when there is no default — see the handler below. """ + if _using_inquirerpy(): + default_percent = f"{default * 100:g}" if default is not None else "" + + def valid_percentage(value: str) -> bool: + try: + percent = float(str(value).rstrip("%")) + except ValueError: + return False + return 0 <= percent <= 100 + + answer = inquirer.text( + message=prompt, + default=default_percent, + instruction="0–100", + validate=valid_percentage, + invalid_message="Enter a number between 0 and 100", + transformer=lambda value: f"{float(str(value).rstrip('%')):g}%", + mandatory=True, + **_inquirer_common(), + ).execute() + return float(str(answer).rstrip("%")) / 100 + hint = f" {escape(f'[{default * 100:g}]')} (enter to accept)" if default is not None else "" while True: try: @@ -589,6 +927,37 @@ def prompt_for_selection( rejects both at once, since j and k are also search characters — so it is opt-in for the pickers that are actually long (model and budget lists), leaving short ones on plain arrow keys. """ + if _using_inquirerpy(): + labels = dict(options) + choices = [ + InquirerChoice(value=value, name=option_label) for value, option_label in options + ] + # InquirerPy's fuzzy control always adds a search-input row. Use it only when the list + # actually needs filtering; otherwise that empty row reads as a duplicate selection cursor. + if searchable and len(options) > _SCROLL_HINT_THRESHOLD: + question = inquirer.fuzzy( + message=prompt, + choices=choices, + pointer="❯", + transformer=lambda selected: labels.get(str(selected), str(selected)), + max_height=_SCROLL_HINT_THRESHOLD, + prompt=_inquirer_filter_prompt(), + info=False, + instruction="type filter · ↑↓ move · enter select", + **_inquirer_common(), + ) + else: + question = inquirer.select( + message=prompt, + choices=choices, + pointer="❯", + transformer=lambda selected: labels.get(str(selected), str(selected)), + max_height=_SCROLL_HINT_THRESHOLD, + instruction="↑↓ move · enter select", + **_inquirer_common(), + ) + return _with_inquirer_rail(question).execute() + style = questionary.Style( [ ("pointer", "fg:cyan bold"), @@ -627,6 +996,19 @@ def prompt_yes_no(prompt: str) -> bool: def prompt_yes_no_default(prompt: str, *, default: bool) -> bool: """Empty answer or closed stdin (EOF) takes ``default`` (no abort on piped runs).""" + if _using_inquirerpy(): + default_label = "Yes" if default else "No" + keys = "Y/n" if default else "y/N" + return bool( + inquirer.confirm( + message=prompt, + default=default, + instruction=f"{keys} · enter selects {default_label}", + transformer=lambda answer: "Yes" if answer else "No", + **_inquirer_common(), + ).execute() + ) + hint = "(Y/n)" if default else "(y/N)" while True: try: diff --git a/tests/test_cli.py b/tests/test_cli.py index d5739ece..17a69431 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1984,6 +1984,33 @@ def test_reconfigure_without_flag_clears_use_pat(self, monkeypatch): assert logins == [(self.WS, "DEFAULT")] assert "use_pat" not in state + def test_verified_connection_skips_duplicate_auth_and_gateway_checks(self, monkeypatch): + cli_mod, logins, ensures, _ = self._stub_deps(monkeypatch, pat_token="dapi-pat") + gateway_checks: list[tuple[str, str]] = [] + token_fetches: list[tuple[str, str | None]] = [] + monkeypatch.setattr( + cli_mod, + "get_databricks_token", + lambda workspace, profile: token_fetches.append((workspace, profile)) or "token", + ) + monkeypatch.setattr( + cli_mod, + "ensure_ai_gateway", + lambda workspace, token: gateway_checks.append((workspace, token)), + ) + + cli_mod.configure_shared_state( + self.WS, + profile="DEFAULT", + skip_model_discovery=True, + connection_checks_verified=True, + ) + + assert logins == [] + assert ensures == [] + assert gateway_checks == [] + assert token_fetches == [(self.WS, "DEFAULT")] + def test_uc_models_used_without_legacy_fallback(self, monkeypatch): # When model-services returns models, they're used and the legacy # per-family discovery is never consulted. diff --git a/tests/test_managed_wizard.py b/tests/test_managed_wizard.py index 2ee299ea..f09d87be 100644 --- a/tests/test_managed_wizard.py +++ b/tests/test_managed_wizard.py @@ -272,7 +272,7 @@ def test_warning_does_not_itemize_the_existing_config(self): ): wizard._handle_existing_config(WORKSPACE, "token") message = warn.call_args[0][0] - assert "one config covers every agent" in message + assert message == "This workspace already has a managed config." for leaked in ("Claude Code", "OpenCode", "lillys_budget", "main.default"): assert leaked not in message, leaked @@ -334,6 +334,96 @@ def test_cancelling_the_picker_aborts(self): wizard._handle_existing_config(WORKSPACE, "token") +class TestSetupStepLayout: + def test_workspace_is_the_first_of_four_steps(self): + assert wizard.SETUP_STEP_TITLES == [ + "Select the workspace", + "Select coding agents", + "Select models for each agent", + "Select the default agent", + ] + + def test_workspace_picker_uses_the_first_step_banner(self): + steps: list[tuple[int, str]] = [] + successes: list[str] = [] + with ( + patch("ucode.cli._prompt_for_configuration", return_value=(WORKSPACE, "p")) as pick_ws, + patch( + "ucode.cli.configure_shared_state", return_value={**STATE, "profile": "p"} + ) as configure, + patch.object( + wizard, "_step_banner", side_effect=lambda i, title: steps.append((i, title)) + ), + patch.object(wizard, "ensure_databricks_auth"), + patch.object(wizard, "get_databricks_token", return_value="token"), + patch.object(wizard, "_require_admin", return_value=True), + patch.object(wizard, "ensure_ai_gateway"), + patch.object(wizard, "_handle_existing_config", return_value=(True, None)), + patch.object(wizard, "check_gateway_endpoint", return_value=True), + patch.object(wizard, "load_managed_state", return_value=None), + patch.object(wizard, "prompt_for_tools", return_value=[]) as pick_agents, + patch.object(wizard, "print_success", side_effect=successes.append), + ): + assert wizard.setup_command() == 0 + + pick_ws.assert_called_once_with(show_section=False, prompt="Workspace:") + assert steps == [(1, "Select the workspace"), (2, "Select coding agents")] + assert pick_agents.call_args.kwargs["prompt"] == "Coding agents:" + assert successes == ["Workspace checks complete"] + assert configure.call_args.kwargs["connection_checks_verified"] is True + + def test_agent_setup_does_not_offer_global_routing(self): + completion_order: list[str] = [] + with ( + patch("ucode.cli._prompt_for_configuration", return_value=(WORKSPACE, "p")), + patch("ucode.cli.configure_shared_state", return_value={**STATE, "profile": "p"}), + patch.object(wizard, "ensure_databricks_auth"), + patch.object(wizard, "get_databricks_token", return_value="token"), + patch.object(wizard, "_require_admin", return_value=True), + patch.object(wizard, "ensure_ai_gateway"), + patch.object(wizard, "_handle_existing_config", return_value=(True, None)), + patch.object(wizard, "check_gateway_endpoint", return_value=True), + patch.object(wizard, "load_managed_state", return_value=None), + patch.object(wizard, "prompt_for_tools", return_value=["codex"]), + patch.object(wizard, "_select_provider_service", return_value=None), + patch.object( + wizard, + "_prompt_models_for_agent", + return_value={"default_model": "system.ai.gpt-5-6"}, + ), + patch.object(wizard, "validate_manifest", return_value=[]), + patch.object(wizard, "save_managed_state") as save, + patch.object( + wizard, + "_render_summary", + side_effect=lambda *_: completion_order.append("summary"), + ), + patch.object( + wizard, + "print_wizard_outro", + side_effect=lambda *_args, **_kwargs: completion_order.append("outro"), + ), + patch.object( + wizard, + "print_success", + side_effect=lambda message: ( + completion_order.append("saved") if message == "Configuration saved" else None + ), + ), + patch.object(wizard, "_print_next_steps"), + patch.object(wizard, "_offer_apply"), + patch.object(wizard, "prompt_yes_no_default") as yes_no, + ): + assert wizard.setup_command() == 0 + + yes_no.assert_not_called() + manifest = save.call_args.args[1] + assert manifest["enabled_agents"]["codex"] == { + "model_config": {"default_model": "system.ai.gpt-5-6"} + } + assert completion_order == ["outro", "saved", "summary"] + + class TestModelPrompting: def test_codex_takes_a_single_model(self): with patch.object(wizard, "prompt_for_selection", return_value="system.ai.gpt-5-6"): @@ -423,6 +513,7 @@ def fake_sel(prompt, options, **kwargs): patch.object(wizard, "_claude_candidates", return_value=candidates), patch.object(wizard, "prompt_for_selection", side_effect=fake_sel), patch.object(wizard, "print_note"), + patch.object(wizard, "print_panel") as panel, ): config = wizard._prompt_models_for_agent("claude", STATE, None) @@ -430,6 +521,7 @@ def fake_sel(prompt, options, **kwargs): # never name a model the config doesn't carry. assert set(prompts[-1]) == {"system.ai.claude-opus-5", "system.ai.claude-sonnet-5"} assert config["default_model"] in config["models"].values() + panel.assert_not_called() def test_claude_single_slot_skips_the_default_prompt(self): candidates = {"opus": ["system.ai.claude-opus-5"]} @@ -1682,6 +1774,8 @@ def test_lists_claude_family_slots(self, capsys): } wizard._render_summary(WORKSPACE, manifest) out = capsys.readouterr().out + assert "Complete configuration" in out + assert "╭" in out and "╯" in out assert "opus" in out and "haiku" in out assert "system.ai.claude-haiku-4-5" in out @@ -1712,8 +1806,9 @@ def test_single_model_agent_needs_no_extra_line(self, capsys): assert "system.ai.gemini-3-flash" in out assert "models:" not in out - def test_scope_label_only_for_global_capable_agents(self, capsys): - # claude/codex can use global settings, so they carry the scope; gemini can't, so it doesn't. + def test_scope_label_only_for_explicit_legacy_setting(self, capsys): + # Imported/older manifests keep their explicit scope visible; newly-authored entries that + # omit the retired setup choice do not get an implied "ucode-only" label. manifest = { "default_agent": "claude", "enabled_agents": { @@ -1722,6 +1817,7 @@ def test_scope_label_only_for_global_capable_agents(self, capsys): "use_as_global_settings": True, }, "gemini": {"model_config": {"default_model": "system.ai.gemini-3-flash"}}, + "codex": {"model_config": {"default_model": "system.ai.gpt-5-6"}}, }, } wizard._render_summary(WORKSPACE, manifest) @@ -1730,6 +1826,8 @@ def test_scope_label_only_for_global_capable_agents(self, capsys): # The gemini line names its model but carries no global-settings/ucode-only scope. gemini_line = next(line for line in out.splitlines() if "gemini-3-flash" in line) assert "ucode-only" not in gemini_line and "global settings" not in gemini_line + codex_line = next(line for line in out.splitlines() if "gpt-5-6" in line) + assert "ucode-only" not in codex_line and "global settings" not in codex_line class TestSetupFromFile: @@ -1818,7 +1916,7 @@ def test_summary_is_boxed(self, capsys): }, ) out = capsys.readouterr().out - assert "Configuration summary" in out + assert "Complete configuration" in out # Rich box-drawing characters: the panel border. assert "╭" in out and "╰" in out assert "system.ai.claude-opus-5" in out @@ -2588,9 +2686,9 @@ def test_apply_declares_yes_and_no_mode_flags(self): command = typer.main.get_command(app).commands["apply"] # type: ignore[attr-defined] declared = {opt for param in command.params for opt in param.opts} assert "--yes" in declared - assert "--dry-run" not in declared assert "--local" not in declared assert "--workspace" not in declared + assert "--dry-run" not in declared def test_apply_publishes_the_latest_draft(self): with patch.object(cli_mod, "apply_command", return_value=0) as apply: diff --git a/tests/test_ui.py b/tests/test_ui.py index c1ef9980..626dcd8c 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -17,11 +17,17 @@ format_meter, format_token_count, format_usd, + inquirerpy_wizard, normalize_workspace_url, + print_success, + print_wizard_header, + print_wizard_outro, + print_wizard_step, prompt_for_multi_selection, prompt_for_percentage, prompt_for_selection, prompt_for_text, + prompt_for_tools, prompt_for_workspace, prompt_yes_no_default, render_box_table, @@ -29,6 +35,177 @@ ) +class TestWizardLayout: + def test_header_is_branded_without_a_box(self, capsys): + print_wizard_header("ucode setup", "Managed defaults") + print_wizard_outro("Ready") + out = capsys.readouterr().out + assert "ucode setup" in out + assert "Managed defaults" in out + assert "┌" in out and "└" in out + assert "╭" not in out and "╰" not in out + + def test_steps_form_a_compact_vertical_rail(self, capsys): + print_wizard_step(1, 4, "Select the workspace") + print_wizard_step(2, 4, "Select coding agents") + out = capsys.readouterr().out + assert "◇ 1/4 Select the workspace" in out + assert "◇ 2/4 Select coding agents" in out + assert "│" in out + assert "╭" not in out and "╰" not in out + + def test_status_output_stays_inside_the_rail(self, capsys): + @inquirerpy_wizard + def render(): + print_wizard_header("ucode setup", "Managed defaults") + print_wizard_step(1, 4, "Select the workspace") + print_success("Admin permissions verified") + print_wizard_outro("Ready") + + render() + status_line = next(line for line in capsys.readouterr().out.splitlines() if "Admin" in line) + assert status_line.startswith("│ ✔") + + def test_inquirer_choice_rows_stay_inside_the_rail(self): + class FakeControl: + def _get_hover_text(self, choice): + return [("class:pointer", "❯"), ("", f" {choice}")] + + def _get_normal_text(self, choice): + return [("", " "), ("", str(choice))] + + class FakeQuestion: + content_control = FakeControl() + + @inquirerpy_wizard + def render(): + print_wizard_header("ucode setup", "Managed defaults") + question = ui_mod._with_inquirer_rail(FakeQuestion()) + return ( + question.content_control._get_hover_text("first"), + question.content_control._get_normal_text("second"), + ) + + hover, normal = render() + assert "".join(text for _, text in hover) == "│ ❯ first" + assert "".join(text for _, text in normal) == "│ second" + + +class TestInquirerPyPrompts: + def test_agent_picker_uses_clack_style_controls_and_summary(self, monkeypatch): + captured: dict = {} + + def fake_checkbox(**kwargs): + captured.update(kwargs) + return _StubInquirerPrompt(["codex", "claude"]) + + monkeypatch.setattr(ui_mod.inquirer, "checkbox", fake_checkbox) + + @inquirerpy_wizard + def run(): + print_wizard_header("ucode setup", "Managed defaults") + result = prompt_for_tools( + [("codex", "Codex"), ("claude", "Claude Code"), ("pi", "Pi")], + preselected=["codex", "claude"], + prompt="Coding agents:", + ) + print_wizard_outro("Ready") + return result + + assert run() == ["codex", "claude"] + assert captured["qmark"] == "│ " + assert captured["enabled_symbol"] == "◉" + assert captured["disabled_symbol"] == "○" + assert captured["transformer"](["codex", "claude"]) == "Codex, Claude Code" + + def test_short_searchable_picker_has_one_cursor_and_no_blank_filter_row(self, monkeypatch): + captured: dict = {} + + def fake_select(**kwargs): + captured.update(kwargs) + return _StubInquirerPrompt("custom") + + monkeypatch.setattr(ui_mod.inquirer, "select", fake_select) + monkeypatch.setattr( + ui_mod.inquirer, + "fuzzy", + lambda **_: pytest.fail("a short picker should not render a fuzzy-search input"), + ) + + @inquirerpy_wizard + def run(): + return prompt_for_selection( + "Default fable model:", + [("fable", "system.ai.claude-fable-5"), ("custom", "Enter a custom model…")], + searchable=True, + ) + + assert run() == "custom" + assert captured["pointer"] == "❯" + + def test_long_fuzzy_picker_labels_its_filter_row(self, monkeypatch): + captured: dict = {} + + def fake_fuzzy(**kwargs): + captured.update(kwargs) + return _StubInquirerPrompt("m0") + + monkeypatch.setattr(ui_mod.inquirer, "fuzzy", fake_fuzzy) + + @inquirerpy_wizard + def run(): + print_wizard_header("ucode setup", "Managed defaults") + result = prompt_for_selection( + "Model:", [(f"m{i}", f"model {i}") for i in range(11)], searchable=True + ) + print_wizard_outro("Ready") + return result + + assert run() == "m0" + assert captured["prompt"] == "│ Filter" + + def test_long_fuzzy_multi_picker_toggles_with_space(self, monkeypatch): + captured: dict = {} + + def fake_fuzzy(**kwargs): + captured.update(kwargs) + return _StubInquirerPrompt(["m0"]) + + monkeypatch.setattr(ui_mod.inquirer, "fuzzy", fake_fuzzy) + + @inquirerpy_wizard + def run(): + print_wizard_header("ucode setup", "Managed defaults") + result = prompt_for_multi_selection( + "Models:", [(f"m{i}", f"model {i}") for i in range(11)], searchable=True + ) + print_wizard_outro("Ready") + return result + + assert run() == ["m0"] + assert captured["prompt"] == "│ Filter" + assert captured["keybindings"] == {"toggle": [{"key": "space"}]} + + def test_confirmation_names_the_default_instead_of_claiming_it_is_highlighted( + self, monkeypatch + ): + captured: dict = {} + + def fake_confirm(**kwargs): + captured.update(kwargs) + return _StubInquirerPrompt(False) + + monkeypatch.setattr(ui_mod.inquirer, "confirm", fake_confirm) + + @inquirerpy_wizard + def run(): + return prompt_yes_no_default("Publish?", default=False) + + assert run() is False + assert captured["instruction"] == "y/N · enter selects No" + assert "highlight" not in captured["instruction"] + + class TestPromptYesNoDefault: def _answer(self, monkeypatch, value): # value: a string the user "types", or EOFError to simulate closed stdin. @@ -354,6 +531,14 @@ def ask(self): return self._answer +class _StubInquirerPrompt: + def __init__(self, answer): + self._answer = answer + + def execute(self): + return self._answer + + class TestPromptForWorkspace: """Capture the choices passed to ``questionary.select`` so we can assert on layout (header alignment + duplicate-host preservation) without driving @@ -397,6 +582,47 @@ def test_shows_header_and_each_profile_row(self, monkeypatch): # Final fallback entry still present. assert choices[3].title == "Enter a different URL" + def test_uses_the_standard_section_panel(self, monkeypatch): + profiles = [("https://a.cloud.databricks.com", "alpha")] + self._capture_select(monkeypatch, answer=profiles[0]) + with patch.object(ui_mod, "print_section") as section: + prompt_for_workspace("Databricks workspace", profiles) + section.assert_called_once_with("Databricks workspace") + + def test_larger_wizard_can_supply_its_own_step_banner(self, monkeypatch): + profiles = [("https://a.cloud.databricks.com", "alpha")] + captured = self._capture_select(monkeypatch, answer=profiles[0]) + with patch.object(ui_mod, "print_section") as section: + prompt_for_workspace("unused", profiles, show_section=False, prompt="Workspace:") + section.assert_not_called() + assert captured["message"] == "Workspace:" + + def test_inquirerpy_workspace_prompt_inherits_the_wizard_rail(self, monkeypatch): + profiles = [("https://a.cloud.databricks.com", "alpha")] + captured: dict = {} + + def fake_select(**kwargs): + captured.update(kwargs) + return _StubInquirerPrompt(profiles[0]) + + monkeypatch.setattr(ui_mod.inquirer, "select", fake_select) + + @inquirerpy_wizard + def run(): + print_wizard_header("ucode setup", "Managed defaults") + result = prompt_for_workspace( + "unused", profiles, show_section=False, prompt="Workspace:" + ) + print_wizard_outro("Ready") + return result + + assert run() == profiles[0] + assert captured["qmark"] == "│ " + assert captured["amark"] == "│ " + assert captured["pointer"] == "❯" + selected_row = "alpha https://a.cloud.databricks.com" + assert captured["transformer"](selected_row) == selected_row + def test_keeps_duplicate_hosts_as_separate_rows(self, monkeypatch): profiles = [ ("https://shared.cloud.databricks.com", "first"), diff --git a/uv.lock b/uv.lock index 0b7d205b..1071ffb6 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "aiohappyeyeballs" version = "2.6.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, @@ -19,7 +19,7 @@ wheels = [ [[package]] name = "aiohttp" version = "3.13.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, @@ -104,7 +104,7 @@ wheels = [ [[package]] name = "aiosignal" version = "1.4.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -117,7 +117,7 @@ wheels = [ [[package]] name = "alembic" version = "1.18.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, @@ -131,7 +131,7 @@ wheels = [ [[package]] name = "annotated-doc" version = "0.0.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, @@ -140,7 +140,7 @@ wheels = [ [[package]] name = "annotated-types" version = "0.7.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, @@ -149,7 +149,7 @@ wheels = [ [[package]] name = "anyio" version = "4.13.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -162,7 +162,7 @@ wheels = [ [[package]] name = "attrs" version = "26.1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, @@ -171,7 +171,7 @@ wheels = [ [[package]] name = "azure-core" version = "1.41.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, @@ -184,7 +184,7 @@ wheels = [ [[package]] name = "azure-storage-blob" version = "12.29.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "azure-core" }, { name = "cryptography" }, @@ -199,7 +199,7 @@ wheels = [ [[package]] name = "azure-storage-file-datalake" version = "12.24.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "azure-core" }, { name = "azure-storage-blob" }, @@ -214,7 +214,7 @@ wheels = [ [[package]] name = "blinker" version = "1.9.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, @@ -223,7 +223,7 @@ wheels = [ [[package]] name = "boto3" version = "1.43.14" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, @@ -237,7 +237,7 @@ wheels = [ [[package]] name = "botocore" version = "1.43.14" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, @@ -251,7 +251,7 @@ wheels = [ [[package]] name = "cachetools" version = "7.1.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, @@ -260,7 +260,7 @@ wheels = [ [[package]] name = "certifi" version = "2026.4.22" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, @@ -269,7 +269,7 @@ wheels = [ [[package]] name = "cffi" version = "2.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] @@ -326,7 +326,7 @@ wheels = [ [[package]] name = "charset-normalizer" version = "3.4.7" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, @@ -399,7 +399,7 @@ wheels = [ [[package]] name = "click" version = "8.3.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] @@ -411,7 +411,7 @@ wheels = [ [[package]] name = "cloudpickle" version = "3.1.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, @@ -420,7 +420,7 @@ wheels = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, @@ -429,7 +429,7 @@ wheels = [ [[package]] name = "contourpy" version = "1.3.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "numpy" }, ] @@ -495,7 +495,7 @@ wheels = [ [[package]] name = "cryptography" version = "46.0.7" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -548,7 +548,7 @@ wheels = [ [[package]] name = "cycler" version = "0.12.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, @@ -557,7 +557,7 @@ wheels = [ [[package]] name = "databricks-agents" version = "1.11.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "boto3" }, { name = "botocore" }, @@ -582,7 +582,7 @@ wheels = [ [[package]] name = "databricks-sdk" version = "0.114.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "google-auth" }, { name = "protobuf" }, @@ -596,7 +596,7 @@ wheels = [ [[package]] name = "databricks-sql-connector" version = "4.2.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "lz4" }, { name = "oauthlib" }, @@ -617,7 +617,7 @@ wheels = [ [[package]] name = "dataclasses-json" version = "0.6.7" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "marshmallow" }, { name = "typing-inspect" }, @@ -630,7 +630,7 @@ wheels = [ [[package]] name = "docker" version = "7.1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "requests" }, @@ -644,7 +644,7 @@ wheels = [ [[package]] name = "et-xmlfile" version = "2.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, @@ -653,7 +653,7 @@ wheels = [ [[package]] name = "fastapi" version = "0.136.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "annotated-doc" }, { name = "pydantic" }, @@ -669,7 +669,7 @@ wheels = [ [[package]] name = "flask" version = "3.1.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "blinker" }, { name = "click" }, @@ -686,7 +686,7 @@ wheels = [ [[package]] name = "flask-cors" version = "6.0.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "flask" }, { name = "werkzeug" }, @@ -699,7 +699,7 @@ wheels = [ [[package]] name = "fonttools" version = "4.63.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, @@ -740,7 +740,7 @@ wheels = [ [[package]] name = "frozenlist" version = "1.8.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, @@ -829,7 +829,7 @@ wheels = [ [[package]] name = "gitdb" version = "4.0.12" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "smmap" }, ] @@ -841,7 +841,7 @@ wheels = [ [[package]] name = "gitpython" version = "3.1.50" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "gitdb" }, ] @@ -853,7 +853,7 @@ wheels = [ [[package]] name = "google-api-core" version = "2.30.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "google-auth" }, { name = "googleapis-common-protos" }, @@ -869,7 +869,7 @@ wheels = [ [[package]] name = "google-auth" version = "2.53.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, @@ -882,7 +882,7 @@ wheels = [ [[package]] name = "google-cloud-core" version = "2.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "google-api-core" }, { name = "google-auth" }, @@ -895,7 +895,7 @@ wheels = [ [[package]] name = "google-cloud-storage" version = "3.10.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "google-api-core" }, { name = "google-auth" }, @@ -912,7 +912,7 @@ wheels = [ [[package]] name = "google-crc32c" version = "1.8.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, @@ -935,7 +935,7 @@ wheels = [ [[package]] name = "google-resumable-media" version = "2.9.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "google-crc32c" }, ] @@ -947,7 +947,7 @@ wheels = [ [[package]] name = "googleapis-common-protos" version = "1.75.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "protobuf" }, ] @@ -959,7 +959,7 @@ wheels = [ [[package]] name = "graphene" version = "3.4.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "graphql-core" }, { name = "graphql-relay" }, @@ -974,7 +974,7 @@ wheels = [ [[package]] name = "graphql-core" version = "3.2.8" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/68/c5/36aa96205c3ecbb3d34c7c24189e4553c7ca2ebc7e1dd07432339b980272/graphql_core-3.2.8.tar.gz", hash = "sha256:015457da5d996c924ddf57a43f4e959b0b94fb695b85ed4c29446e508ed65cf3", size = 513181, upload-time = "2026-03-05T19:55:37.332Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/86/41/cb887d9afc5dabd78feefe6ccbaf83ff423c206a7a1b7aeeac05120b2125/graphql_core-3.2.8-py3-none-any.whl", hash = "sha256:cbee07bee1b3ed5e531723685369039f32ff815ef60166686e0162f540f1520c", size = 207349, upload-time = "2026-03-05T19:55:35.911Z" }, @@ -983,7 +983,7 @@ wheels = [ [[package]] name = "graphql-relay" version = "3.2.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "graphql-core" }, ] @@ -995,13 +995,15 @@ wheels = [ [[package]] name = "greenlet" version = "3.5.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/7c/6c/de5b1b388cd2d9fbdfeab324863daba37d54e6e233ddbefd70b385a8c591/greenlet-3.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89101bfd5011e069be974903cb3a4e4523845e4ece2d62dcd8d358933c0ef249", size = 620094, upload-time = "2026-05-20T14:09:09.18Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/4a/43/1204baffab8a6476464795a7ccf394a3248d4f22c9f87173a15b36b6d971/greenlet-3.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:e6cd99ea59dd5d89f0c956606571d79bfe6f68c9eb7f4a4083a41a7f1587edee", size = 422782, upload-time = "2026-05-20T14:01:39.597Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, @@ -1009,7 +1011,9 @@ wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/19/ba/c24110c55dffa55aa6e1d98b45310da33801aeba7686ff0190fe5d46fd32/greenlet-3.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce", size = 622911, upload-time = "2026-05-20T14:09:10.598Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/ec/7b/d20db2e8a5ad6c038702f3179b136f93f0a3d1a21a0c0777f3e470cdf4b2/greenlet-3.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436", size = 425228, upload-time = "2026-05-20T14:01:40.837Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, @@ -1017,7 +1021,9 @@ wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/c6/2d/2d80842910da44f78c286532d084b8a5c3717c844ae80ceb3858738ae89a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c", size = 667767, upload-time = "2026-05-20T14:09:12.15Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/e9/d3/dad2eecedfbb1ed7050a20dcfae40c1442b74bc7423608be2c7e03ee7133/greenlet-3.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d", size = 470786, upload-time = "2026-05-20T14:01:42.064Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, @@ -1025,14 +1031,18 @@ wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/8c/46/5987dcd1a2570ba84f3b187536b2ca3ae97613387e57f5cfa99df068fe5e/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f", size = 656607, upload-time = "2026-05-20T14:09:13.949Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/2c/c1/6da0a9ddcc29d7e51ef14883fa3dc1e53b3f4ffba00582106c7bf55da1d8/greenlet-3.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de", size = 488287, upload-time = "2026-05-20T14:01:43.143Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/dc/74/807a047255bf1e09303627c46dc043dca596b6958a354d904f32ab382005/greenlet-3.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0", size = 672962, upload-time = "2026-05-20T14:09:15.532Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/76/32/19d4e13225193c29b13e308015223f7d75fd3d8623d49dd19040d2ce8ec1/greenlet-3.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc", size = 476047, upload-time = "2026-05-20T14:01:44.39Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, @@ -1040,7 +1050,9 @@ wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/c9/9d/1dcdf7b95ab3cf8c7b6d7277c18a5e167312f2b362ddfcc5d5e6d8d84b43/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c", size = 659998, upload-time = "2026-05-20T14:09:16.912Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://pypi-proxy.cloud.databricks.com/packages/05/7e/c4959664fc231d587d66d8e81f2095e98056ba1954beafdcbe635e251052/greenlet-3.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62", size = 494470, upload-time = "2026-05-20T14:01:45.611Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, @@ -1050,7 +1062,7 @@ wheels = [ [[package]] name = "gunicorn" version = "25.3.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "packaging" }, ] @@ -1062,7 +1074,7 @@ wheels = [ [[package]] name = "h11" version = "0.16.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, @@ -1071,7 +1083,7 @@ wheels = [ [[package]] name = "httpcore" version = "1.0.9" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "certifi" }, { name = "h11" }, @@ -1084,7 +1096,7 @@ wheels = [ [[package]] name = "httpx" version = "0.28.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "anyio" }, { name = "certifi" }, @@ -1099,7 +1111,7 @@ wheels = [ [[package]] name = "httpx-sse" version = "0.4.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, @@ -1108,7 +1120,7 @@ wheels = [ [[package]] name = "huey" version = "2.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/fe/29/3428d52eb8e85025e264a291641a9f9d6407cc1e51d1b630f6ac5815999a/huey-2.6.0.tar.gz", hash = "sha256:8d11f8688999d65266af1425b831f6e3773e99415027177b8734b0ffd5e251f6", size = 221068, upload-time = "2026-01-06T03:01:02.055Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/1a/34/fae9ac8f1c3a552fd3f7ff652b94c78d219dedc5fce0c0a4232457760a00/huey-2.6.0-py3-none-any.whl", hash = "sha256:1b9df9d370b49c6d5721ba8a01ac9a787cf86b3bdc584e4679de27b920395c3f", size = 76951, upload-time = "2026-01-06T03:01:00.808Z" }, @@ -1117,7 +1129,7 @@ wheels = [ [[package]] name = "idna" version = "3.13" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, @@ -1126,7 +1138,7 @@ wheels = [ [[package]] name = "importlib-metadata" version = "9.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "zipp" }, ] @@ -1138,16 +1150,29 @@ wheels = [ [[package]] name = "iniconfig" version = "2.3.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "inquirerpy" +version = "0.3.4" +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +dependencies = [ + { name = "pfzy" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/64/73/7570847b9da026e07053da3bbe2ac7ea6cde6bb2cbd3c7a5a950fa0ae40b/InquirerPy-0.3.4.tar.gz", hash = "sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e", size = 44431, upload-time = "2022-06-27T23:11:20.598Z" } +wheels = [ + { url = "https://pypi-proxy.cloud.databricks.com/packages/ce/ff/3b59672c47c6284e8005b42e84ceba13864aa0f39f067c973d1af02f5d91/InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4", size = 67677, upload-time = "2022-06-27T23:11:17.723Z" }, +] + [[package]] name = "isodate" version = "0.7.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, @@ -1156,7 +1181,7 @@ wheels = [ [[package]] name = "itsdangerous" version = "2.2.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, @@ -1165,7 +1190,7 @@ wheels = [ [[package]] name = "jinja2" version = "3.1.6" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "markupsafe" }, ] @@ -1177,7 +1202,7 @@ wheels = [ [[package]] name = "jmespath" version = "1.1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, @@ -1186,7 +1211,7 @@ wheels = [ [[package]] name = "joblib" version = "1.5.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, @@ -1195,7 +1220,7 @@ wheels = [ [[package]] name = "jsonschema" version = "4.26.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, @@ -1210,7 +1235,7 @@ wheels = [ [[package]] name = "jsonschema-specifications" version = "2025.9.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "referencing" }, ] @@ -1222,7 +1247,7 @@ wheels = [ [[package]] name = "kiwisolver" version = "1.5.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, @@ -1308,7 +1333,7 @@ wheels = [ [[package]] name = "lz4" version = "4.4.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/57/51/f1b86d93029f418033dddf9b9f79c8d2641e7454080478ee2aab5123173e/lz4-4.4.5.tar.gz", hash = "sha256:5f0b9e53c1e82e88c10d7c180069363980136b9d7a8306c4dca4f760d60c39f0", size = 172886, upload-time = "2025-11-03T13:02:36.061Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/1b/ac/016e4f6de37d806f7cc8f13add0a46c9a7cfc41a5ddc2bc831d7954cf1ce/lz4-4.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5aa4cead2044bab83e0ebae56e0944cc7fcc1505c7787e9e1057d6d549897e", size = 207163, upload-time = "2025-11-03T13:01:45.895Z" }, @@ -1348,7 +1373,7 @@ wheels = [ [[package]] name = "mako" version = "1.3.12" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "markupsafe" }, ] @@ -1360,7 +1385,7 @@ wheels = [ [[package]] name = "markdown-it-py" version = "4.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "mdurl" }, ] @@ -1372,7 +1397,7 @@ wheels = [ [[package]] name = "markupsafe" version = "3.0.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, @@ -1435,7 +1460,7 @@ wheels = [ [[package]] name = "marshmallow" version = "3.26.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "packaging" }, ] @@ -1447,7 +1472,7 @@ wheels = [ [[package]] name = "matplotlib" version = "3.10.9" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "contourpy" }, { name = "cycler" }, @@ -1501,7 +1526,7 @@ wheels = [ [[package]] name = "mcp" version = "1.28.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "anyio" }, { name = "httpx" }, @@ -1526,7 +1551,7 @@ wheels = [ [[package]] name = "mdurl" version = "0.1.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, @@ -1535,7 +1560,7 @@ wheels = [ [[package]] name = "mlflow" version = "3.12.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "aiohttp" }, { name = "alembic" }, @@ -1575,7 +1600,7 @@ databricks = [ [[package]] name = "mlflow-skinny" version = "3.12.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "cachetools" }, { name = "click" }, @@ -1606,7 +1631,7 @@ wheels = [ [[package]] name = "mlflow-tracing" version = "3.12.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "cachetools" }, { name = "databricks-sdk" }, @@ -1625,7 +1650,7 @@ wheels = [ [[package]] name = "multidict" version = "6.7.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, @@ -1724,7 +1749,7 @@ wheels = [ [[package]] name = "mypy-extensions" version = "1.1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, @@ -1733,7 +1758,7 @@ wheels = [ [[package]] name = "numpy" version = "2.4.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, @@ -1794,7 +1819,7 @@ wheels = [ [[package]] name = "oauthlib" version = "3.3.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, @@ -1803,7 +1828,7 @@ wheels = [ [[package]] name = "openpyxl" version = "3.1.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "et-xmlfile" }, ] @@ -1815,7 +1840,7 @@ wheels = [ [[package]] name = "opentelemetry-api" version = "1.42.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "typing-extensions" }, ] @@ -1827,7 +1852,7 @@ wheels = [ [[package]] name = "opentelemetry-proto" version = "1.42.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "protobuf" }, ] @@ -1839,7 +1864,7 @@ wheels = [ [[package]] name = "opentelemetry-sdk" version = "1.42.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, @@ -1853,7 +1878,7 @@ wheels = [ [[package]] name = "opentelemetry-semantic-conventions" version = "0.63b1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, @@ -1866,7 +1891,7 @@ wheels = [ [[package]] name = "packaging" version = "26.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, @@ -1875,7 +1900,7 @@ wheels = [ [[package]] name = "pandas" version = "2.3.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, @@ -1919,10 +1944,19 @@ wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, ] +[[package]] +name = "pfzy" +version = "0.3.4" +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } +sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/d9/5a/32b50c077c86bfccc7bed4881c5a2b823518f5450a30e639db5d3711952e/pfzy-0.3.4.tar.gz", hash = "sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1", size = 8396, upload-time = "2022-01-28T02:26:17.946Z" } +wheels = [ + { url = "https://pypi-proxy.cloud.databricks.com/packages/8c/d7/8ff98376b1acc4503253b685ea09981697385ce344d4e3935c2af49e044d/pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96", size = 8537, upload-time = "2022-01-28T02:26:16.047Z" }, +] + [[package]] name = "pillow" version = "12.2.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, @@ -1991,7 +2025,7 @@ wheels = [ [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, @@ -2000,7 +2034,7 @@ wheels = [ [[package]] name = "prettytable" version = "3.17.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "wcwidth" }, ] @@ -2012,7 +2046,7 @@ wheels = [ [[package]] name = "prompt-toolkit" version = "3.0.52" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "wcwidth" }, ] @@ -2024,7 +2058,7 @@ wheels = [ [[package]] name = "propcache" version = "0.5.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, @@ -2118,7 +2152,7 @@ wheels = [ [[package]] name = "proto-plus" version = "1.28.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "protobuf" }, ] @@ -2130,7 +2164,7 @@ wheels = [ [[package]] name = "protobuf" version = "6.33.6" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, @@ -2145,7 +2179,7 @@ wheels = [ [[package]] name = "pyarrow" version = "23.0.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" }, @@ -2188,7 +2222,7 @@ wheels = [ [[package]] name = "pyasn1" version = "0.6.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, @@ -2197,7 +2231,7 @@ wheels = [ [[package]] name = "pyasn1-modules" version = "0.4.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "pyasn1" }, ] @@ -2209,7 +2243,7 @@ wheels = [ [[package]] name = "pybreaker" version = "1.4.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f2/89/fbf98e383f1ec6d117af2cd983efdb3eb7018b63834c427025764194cac2/pybreaker-1.4.1.tar.gz", hash = "sha256:8df2d245c73ba40c8242c56ffb4f12138fbadc23e296224740c2028ea9dc1178", size = 15555, upload-time = "2025-09-21T15:12:04.499Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/44/75/e64d3d40a741e2be21d69154f4e5c43a66f0c603c5ef11f49e01429a5932/pybreaker-1.4.1-py3-none-any.whl", hash = "sha256:b4dab4a05195b7f2a64a6c1a6c4ba7a96534ef56ea7210e6bcb59f28897160e0", size = 12915, upload-time = "2025-09-21T15:12:02.284Z" }, @@ -2218,7 +2252,7 @@ wheels = [ [[package]] name = "pycparser" version = "3.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, @@ -2227,7 +2261,7 @@ wheels = [ [[package]] name = "pydantic" version = "2.13.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, @@ -2242,7 +2276,7 @@ wheels = [ [[package]] name = "pydantic-core" version = "2.46.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "typing-extensions" }, ] @@ -2317,7 +2351,7 @@ wheels = [ [[package]] name = "pydantic-settings" version = "2.14.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, @@ -2331,7 +2365,7 @@ wheels = [ [[package]] name = "pygments" version = "2.20.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, @@ -2340,7 +2374,7 @@ wheels = [ [[package]] name = "pyjwt" version = "2.12.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, @@ -2354,7 +2388,7 @@ crypto = [ [[package]] name = "pyparsing" version = "3.3.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, @@ -2363,7 +2397,7 @@ wheels = [ [[package]] name = "pytest" version = "9.0.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, @@ -2379,7 +2413,7 @@ wheels = [ [[package]] name = "python-dateutil" version = "2.9.0.post0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "six" }, ] @@ -2391,7 +2425,7 @@ wheels = [ [[package]] name = "python-dotenv" version = "1.2.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, @@ -2400,7 +2434,7 @@ wheels = [ [[package]] name = "python-multipart" version = "0.0.32" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, @@ -2409,7 +2443,7 @@ wheels = [ [[package]] name = "pytz" version = "2026.1.post1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, @@ -2418,7 +2452,7 @@ wheels = [ [[package]] name = "pywin32" version = "311" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://pypi-proxy.cloud.databricks.com/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, @@ -2434,7 +2468,7 @@ wheels = [ [[package]] name = "pyyaml" version = "6.0.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, @@ -2480,7 +2514,7 @@ wheels = [ [[package]] name = "questionary" version = "2.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "prompt-toolkit" }, ] @@ -2492,7 +2526,7 @@ wheels = [ [[package]] name = "referencing" version = "0.37.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, @@ -2506,7 +2540,7 @@ wheels = [ [[package]] name = "regex" version = "2026.5.9" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, @@ -2594,7 +2628,7 @@ wheels = [ [[package]] name = "requests" version = "2.33.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, @@ -2609,7 +2643,7 @@ wheels = [ [[package]] name = "rich" version = "15.0.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, @@ -2622,7 +2656,7 @@ wheels = [ [[package]] name = "rpds-py" version = "2026.6.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, @@ -2718,7 +2752,7 @@ wheels = [ [[package]] name = "ruff" version = "0.15.12" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, @@ -2743,7 +2777,7 @@ wheels = [ [[package]] name = "s3transfer" version = "0.17.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "botocore" }, ] @@ -2755,7 +2789,7 @@ wheels = [ [[package]] name = "scikit-learn" version = "1.8.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "joblib" }, { name = "numpy" }, @@ -2799,7 +2833,7 @@ wheels = [ [[package]] name = "scipy" version = "1.17.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "numpy" }, ] @@ -2860,7 +2894,7 @@ wheels = [ [[package]] name = "shellingham" version = "1.5.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, @@ -2869,7 +2903,7 @@ wheels = [ [[package]] name = "six" version = "1.17.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, @@ -2878,7 +2912,7 @@ wheels = [ [[package]] name = "skops" version = "0.14.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "numpy" }, { name = "packaging" }, @@ -2894,7 +2928,7 @@ wheels = [ [[package]] name = "smmap" version = "5.0.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, @@ -2903,7 +2937,7 @@ wheels = [ [[package]] name = "sqlalchemy" version = "2.0.50" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, @@ -2944,7 +2978,7 @@ wheels = [ [[package]] name = "sqlparse" version = "0.5.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, @@ -2953,7 +2987,7 @@ wheels = [ [[package]] name = "sse-starlette" version = "3.4.5" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "anyio" }, { name = "starlette" }, @@ -2966,7 +3000,7 @@ wheels = [ [[package]] name = "starlette" version = "0.52.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -2979,7 +3013,7 @@ wheels = [ [[package]] name = "tenacity" version = "9.1.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, @@ -2988,7 +3022,7 @@ wheels = [ [[package]] name = "threadpoolctl" version = "3.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, @@ -2997,7 +3031,7 @@ wheels = [ [[package]] name = "thrift" version = "0.20.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "six" }, ] @@ -3006,7 +3040,7 @@ sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/3c/2d/8946864f [[package]] name = "tiktoken" version = "0.13.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "regex" }, { name = "requests" }, @@ -3053,7 +3087,7 @@ wheels = [ [[package]] name = "tomlkit" version = "0.14.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, @@ -3062,7 +3096,7 @@ wheels = [ [[package]] name = "tqdm" version = "4.67.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] @@ -3074,7 +3108,7 @@ wheels = [ [[package]] name = "ty" version = "0.0.33" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/84/44/9478c50c266826c1bf30d1692e589755bffa8f1c0a3eb7af8a346c255991/ty-0.0.33.tar.gz", hash = "sha256:46d63bda07403322cb6c28ccfdd5536be916e13df725c29f7ccd0a21f06bd9e8", size = 5559373, upload-time = "2026-04-28T10:45:13.18Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/e9/24/e287388c63a19191be26b32ff4dbd06029834068150ebe2532939bc4c851/ty-0.0.33-py3-none-linux_armv6l.whl", hash = "sha256:94d0a9d2234261a8911396d59e506b5923fe0971dbda43b9dcea287936887fcc", size = 11021308, upload-time = "2026-04-28T10:45:43.34Z" }, @@ -3098,7 +3132,7 @@ wheels = [ [[package]] name = "typer" version = "0.24.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "annotated-doc" }, { name = "click" }, @@ -3113,7 +3147,7 @@ wheels = [ [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, @@ -3122,7 +3156,7 @@ wheels = [ [[package]] name = "typing-inspect" version = "0.9.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, @@ -3135,7 +3169,7 @@ wheels = [ [[package]] name = "typing-inspection" version = "0.4.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "typing-extensions" }, ] @@ -3147,7 +3181,7 @@ wheels = [ [[package]] name = "tzdata" version = "2026.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, @@ -3159,6 +3193,7 @@ source = { editable = "." } dependencies = [ { name = "databricks-sql-connector" }, { name = "httpx" }, + { name = "inquirerpy" }, { name = "mcp" }, { name = "questionary" }, { name = "tomlkit" }, @@ -3181,6 +3216,7 @@ dev = [ requires-dist = [ { name = "databricks-sql-connector", specifier = ">=3.6.0" }, { name = "httpx", specifier = ">=0.27.1" }, + { name = "inquirerpy", specifier = ">=0.3.4" }, { name = "mcp", specifier = ">=1.28.0" }, { name = "mlflow", extras = ["databricks"], marker = "extra == 'tracing'", specifier = ">=3.4" }, { name = "questionary", specifier = ">=2.0.0" }, @@ -3199,7 +3235,7 @@ dev = [ [[package]] name = "urllib3" version = "2.6.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, @@ -3208,7 +3244,7 @@ wheels = [ [[package]] name = "uvicorn" version = "0.48.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "click" }, { name = "h11" }, @@ -3221,7 +3257,7 @@ wheels = [ [[package]] name = "waitress" version = "3.0.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/bf/cb/04ddb054f45faa306a230769e868c28b8065ea196891f09004ebace5b184/waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f", size = 179901, upload-time = "2024-11-16T20:02:35.195Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, @@ -3230,7 +3266,7 @@ wheels = [ [[package]] name = "wcwidth" version = "0.6.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, @@ -3239,7 +3275,7 @@ wheels = [ [[package]] name = "werkzeug" version = "3.1.8" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "markupsafe" }, ] @@ -3251,7 +3287,7 @@ wheels = [ [[package]] name = "whenever" version = "0.7.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] @@ -3290,7 +3326,7 @@ wheels = [ [[package]] name = "yarl" version = "1.24.2" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "idna" }, { name = "multidict" }, @@ -3372,7 +3408,7 @@ wheels = [ [[package]] name = "zipp" version = "4.1.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://pypi-proxy.cloud.databricks.com/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } wheels = [ { url = "https://pypi-proxy.cloud.databricks.com/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" },