Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/build_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ jobs:
run: |
uv sync --extra docs --frozen

# TEMPORARY: sphinx-click cannot introspect typer's vendored click (typer
# >= 0.26), which breaks the CLI reference pages. Pin to the last real-click
# typer for the docs build only (does not affect the shipped package) until
# we settle on a long-term docs fix. See the docs CLI reference pages.
- name: Pin typer < 0.26 for docs build (sphinx-click compatibility)
run: uv pip install 'typer<0.26'

- name: Build docs
working-directory: docs
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: |
build_config:
target_platform: "native"
# conda-forge has binaries for gmsh and friends, even on ARM64
base_image: "condaforge/miniforge3:latest"
base_image: "condaforge/miniforge3:26.1.1-3"
requirements:
provider: conda
extra_packages:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: |
build_config:
target_platform: "native"
# conda-forge has binaries for gmsh and friends, even on ARM64
base_image: "condaforge/miniforge3:latest"
base_image: "condaforge/miniforge3:26.1.1-3"
requirements:
provider: conda
extra_packages:
Expand Down
2 changes: 1 addition & 1 deletion examples/conda/tesseract_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ version: "1.0.0"
description: "Custom venv creation."

build_config:
base_image: "condaforge/miniforge3:latest"
base_image: "condaforge/miniforge3:26.1.1-3"
requirements:
provider: conda
451 changes: 280 additions & 171 deletions production.uv.lock

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ authors = [
license = { text = "Apache-2.0" }
requires-python = ">=3.10,<3.15"
dependencies = [
"click",
"jinja2",
"rich",
"typer",
"typer>=0.16",
"pyyaml",
"pydantic",
"numpy",
Expand Down Expand Up @@ -44,8 +43,10 @@ runtime = [
"fastapi<=0.136.3,>=0.115",
"requests<=2.34.2,>=2.32.4",
"uvicorn<=0.48.0,>=0.34",
"click<=8.4.1,>=8.1",
"typer<=0.25.1,>=0.15",
# NOTE: typer 0.15.0 ships a TyperArgument.make_metavar() without the ``ctx``
# arg that click >= 8.2 passes, and declares no click upper bound, so it is
# broken with modern click. 0.16.0 is the first version that supports it.
"typer<=0.26.4,>=0.16",
"fsspec[http,s3]<=2026.4.0,>=2024.12",
"pybase64<=1.4.3,>=1.4",
"orjson<=3.11.9,>=3.10",
Expand Down
12 changes: 4 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
click==8.4.1
# via
# tesseract-core
# typer
colorama==0.4.6 ; sys_platform == 'win32'
# via click
idna==3.16
# via typer
idna==3.17
# via requests
jinja2==3.1.6
# via tesseract-core
Expand All @@ -34,7 +30,7 @@ orjson==3.11.9
# via tesseract-core
packaging==26.2
# via tesseract-core
pip==26.1.1
pip==26.1.2
# via tesseract-core
pybase64==1.4.3
# via tesseract-core
Expand All @@ -54,7 +50,7 @@ rich==15.0.0
# typer
shellingham==1.5.4
# via typer
typer==0.25.1
typer==0.26.4
# via tesseract-core
typing-extensions==4.15.0
# via
Expand Down
18 changes: 13 additions & 5 deletions tesseract_core/runtime/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
get_origin,
)

import click
import typer
from pydantic import ValidationError
from pydantic_core import from_json
Expand All @@ -45,6 +44,15 @@
check_gradients as check_gradients_,
)

# typer >= 0.26 vendors its own click (and drops the click dependency), so the
# Context type and exceptions must come from the click typer actually runs;
# fall back to real click on older typer (which still ships it).
try:
from typer._click.core import Context
from typer._click.exceptions import BadParameter, UsageError
except ImportError: # typer < 0.26
from click import BadParameter, Context, UsageError

CONFIG_FIELDS = {
str(field_name): field.annotation
for field_name, field in RuntimeConfig.model_fields.items()
Expand All @@ -66,7 +74,7 @@ def _enum_to_val(val: Any) -> Any:
class SpellcheckedTyperGroup(typer.core.TyperGroup):
"""A Typer group that suggests similar commands if a command is not found."""

def get_command(self, ctx: click.Context, invoked_command: str) -> Any:
def get_command(self, ctx: Context, invoked_command: str) -> Any:
"""Get a command from the Typer group, suggesting similar commands if the command is not found."""
import difflib

Expand All @@ -76,7 +84,7 @@ def get_command(self, ctx: click.Context, invoked_command: str) -> Any:
invoked_command, possible_commands, n=1, cutoff=0.6
)
if close_match:
raise click.UsageError(
raise UsageError(
f"No such command '{invoked_command}'. Did you mean '{close_match[0]}'?",
ctx,
)
Expand Down Expand Up @@ -111,7 +119,7 @@ def _parse_payload(value: Any) -> dict[str, Any]:
try:
value = read_from_path(value[1:]).decode("utf-8")
except Exception as e:
raise click.BadParameter(f"Could not read data from path {value}.") from e
raise BadParameter(f"Could not read data from path {value}.") from e

# Use pydantic from_json here because it is much faster, and the payload may be large.
return from_json(value)
Expand Down Expand Up @@ -407,7 +415,7 @@ def _callback_wrapper(**kwargs: Any):
context={"base_dir": input_path},
)
except ValidationError as e:
raise click.BadParameter(
raise BadParameter(
str(e),
param_hint="payload",
) from e
Expand Down
19 changes: 14 additions & 5 deletions tesseract_core/sdk/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from types import SimpleNamespace
from typing import Annotated, Any, NoReturn

import click
import typer
import yaml
from jinja2 import Environment, PackageLoader, StrictUndefined
Expand Down Expand Up @@ -48,6 +47,16 @@
from .exceptions import UserError
from .logs import DEFAULT_CONSOLE, set_logger

# typer >= 0.26 vendors its own click (and drops the click dependency), so the
# Context type, exceptions, and context lookups must come from the click typer
# actually runs; fall back to real click on older typer (which still ships it).
try:
from typer._click.core import Context
from typer._click.exceptions import UsageError
from typer._click.globals import get_current_context
except ImportError: # typer < 0.26
from click import Context, UsageError, get_current_context

logger = getLogger("tesseract")

# Jinja2 Template Environment
Expand All @@ -62,7 +71,7 @@
class SpellcheckedTyperGroup(typer.core.TyperGroup):
"""A Typer group that suggests similar commands if a command is not found."""

def get_command(self, ctx: click.Context, invoked_command: str) -> Any:
def get_command(self, ctx: Context, invoked_command: str) -> Any:
"""Get a command from the Typer group, suggesting similar commands if the command is not found."""
import difflib

Expand All @@ -72,7 +81,7 @@ def get_command(self, ctx: click.Context, invoked_command: str) -> Any:
invoked_command, possible_commands, n=1, cutoff=0.6
)
if close_match:
raise click.UsageError(
raise UsageError(
f"No such command '{invoked_command}'. Did you mean '{close_match[0]}'?",
ctx,
)
Expand Down Expand Up @@ -1124,7 +1133,7 @@ def run_container(

if not tesseract_image:
if invoke_help:
click.get_current_context().get_help()
get_current_context().get_help()
return
raise typer.BadParameter(
"Tesseract image name is required.",
Expand All @@ -1133,7 +1142,7 @@ def run_container(

if not cmd:
if invoke_help:
click.get_current_context().get_help()
get_current_context().get_help()
return
else:
error_string = f"Command is required. Are you sure your Tesseract image name is `{tesseract_image}`?"
Expand Down
Loading