Skip to content

Commit d1709bc

Browse files
committed
feat: Add max_content_width to create_cli() for improved help formatting and optimize simplify_ids prefix generation for shorter module IDs.
1 parent f488074 commit d1709bc

File tree

5 files changed

+61
-8
lines changed

5 files changed

+61
-8
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.3.1] - 2026-03-20
6+
7+
### Added
8+
- **`max_content_width` parameter on `create_cli()`** -- overrides Click's default terminal-width-based help formatting. When the terminal is narrow, Click calculates description column width from the longest command name, often leaving zero space for descriptions (shown as `...`). This parameter forces a wider layout so descriptions are always visible.
9+
10+
### Improved
11+
- **`simplify_ids` prefix optimization** -- when `simplify_ids=True`, module ID prefix now uses only the first path segment instead of all segments. This produces shorter, cleaner command names while maintaining uniqueness via function name differentiation and `deduplicate_ids()` safety net.
12+
- Before: `credit_purchase.purchase.status.get_purchase_status_by_payment_intent.get` (73 chars)
13+
- After: `credit_purchase.get_purchase_status_by_payment_intent.get` (57 chars)
14+
- Default (`simplify_ids=False`) behavior unchanged -- all path segments preserved for backward compatibility.
15+
516
## [0.3.0] - 2026-03-20
617

718
### Added

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ cli = apcore.create_cli(
285285
prog_name="myapp-cli",
286286
base_url="http://localhost:8000",
287287
simplify_ids=True,
288-
help_text_max_length=500,
288+
max_content_width=160, # wider help output for long command names
289289
)
290290
291291
if __name__ == "__main__":
@@ -451,7 +451,14 @@ scanner = get_scanner("native")
451451
modules = scanner.scan(app, include=r"users\.", exclude=r"\.delete$")
452452
```
453453

454-
The `simplify_ids` option extracts the original Python function name from FastAPI's auto-generated operationId, producing much shorter and more readable module IDs. It defaults to `False` for backward compatibility.
454+
The `simplify_ids` option extracts the original Python function name from FastAPI's auto-generated operationId and uses only the first path segment as prefix, producing much shorter and more readable module IDs:
455+
456+
```
457+
Default: credit_purchase.purchase.status.get_purchase_status_by_payment_intent.get (73 chars)
458+
simplify_ids: credit_purchase.get_purchase_status_by_payment_intent.get (57 chars)
459+
```
460+
461+
It defaults to `False` for backward compatibility.
455462

456463
Users only need to interact with two things:
457464
- **`FastAPIApcore`** -- the unified entry point (import from `fastapi_apcore`)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "fastapi-apcore"
7-
version = "0.3.0"
7+
version = "0.3.1"
88
description = "FastAPI integration for apcore AI-Perceivable Core — exposes FastAPI routes as apcore modules with auto-discovery, schema extraction from Pydantic models, and OpenAPI-based scanning."
99
requires-python = ">=3.11"
1010
readme = "README.md"

src/fastapi_apcore/client.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ def create_cli(
533533
include: str | None = None,
534534
exclude: str | None = None,
535535
help_text_max_length: int = 1000,
536+
max_content_width: int | None = None,
536537
) -> Any:
537538
"""Create an apcore-cli Click group with all API routes as commands.
538539
@@ -553,6 +554,9 @@ def create_cli(
553554
exclude: Regex pattern — skip matching module IDs.
554555
help_text_max_length: Max characters for CLI help text per
555556
command. Defaults to 1000.
557+
max_content_width: Maximum width for CLI help output. When set,
558+
overrides Click's default (terminal width, max 80). Useful
559+
when command names are long and truncate descriptions.
556560
557561
Returns:
558562
A Click Group that can be invoked with ``cli(standalone_mode=True)``.
@@ -609,13 +613,25 @@ def create_cli(
609613
executor = Executor(registry)
610614

611615
# 3. Build Click group
616+
ctx_settings: dict[str, Any] = {}
617+
if max_content_width is not None:
618+
ctx_settings["max_content_width"] = max_content_width
619+
620+
# Compute the effective help width for the command list.
621+
# Click uses min(terminal_width, max_content_width), so when the
622+
# terminal is narrow, long command names push the description column
623+
# to zero and everything shows "...". We override format_commands
624+
# to use the configured max_content_width directly.
625+
effective_width = max_content_width
626+
612627
@click.group(
613628
cls=LazyModuleGroup,
614629
registry=registry,
615630
executor=executor,
616631
help_text_max_length=help_text_max_length,
617632
name=prog_name,
618633
help=f"{prog_name} — CLI for {app.title or 'FastAPI'} API.",
634+
context_settings=ctx_settings,
619635
)
620636
@click.version_option(
621637
version=app.version or "0.0.0",
@@ -638,6 +654,19 @@ def cli(log_level: str | None = None) -> None:
638654
register_discovery_commands(cli, registry)
639655
register_shell_commands(cli, prog_name=prog_name)
640656

657+
# Override format_commands to use effective_width so descriptions
658+
# are not truncated when the terminal is narrower than the content.
659+
if effective_width is not None:
660+
_original_format_commands = cli.format_commands
661+
662+
def _wide_format_commands(ctx: Any, formatter: Any) -> None:
663+
original_width = formatter.width
664+
formatter.width = max(formatter.width, effective_width)
665+
_original_format_commands(ctx, formatter)
666+
formatter.width = original_width
667+
668+
cli.format_commands = _wide_format_commands # type: ignore[method-assign]
669+
641670
return cli
642671

643672
def to_openai_tools(

src/fastapi_apcore/scanners/openapi.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,17 @@ def get_source_name(self) -> str:
121121
return "openapi-fastapi"
122122

123123
def _generate_module_id(self, operation: dict[str, Any], path: str, method: str) -> str:
124-
"""Generate module_id from tags + function_name + method.
124+
"""Generate module_id from prefix + function_name + method.
125125
126126
When ``simplify_ids=True`` (set in constructor), extracts the clean
127-
function name from FastAPI's operationId::
127+
function name and uses only the first path segment as prefix::
128128
129-
GET /product/{product_id} → product.get_product.get
130-
POST /task/create → task.create_task.post
129+
GET /product/{product_id} → product.get_product.get
130+
POST /task/create → task.create_task.post
131+
GET /virtual-purchase/purchase/status/{id} → virtual_purchase.get_purchase_status_by_payment_intent.get
131132
132133
When ``simplify_ids=False`` (default), uses the raw operationId
133-
with only the trailing method stripped::
134+
with only the trailing method stripped, and all path segments as prefix::
134135
135136
GET /product/{product_id} → product.get_product_product__product_id_.get
136137
"""
@@ -144,7 +145,12 @@ def _generate_module_id(self, operation: dict[str, Any], path: str, method: str)
144145

145146
if tags:
146147
prefix = str(tags[0]).lower().replace(" ", "_")
148+
elif self._simplify_ids:
149+
# Only first path segment as prefix — shorter, still unique
150+
path_parts = [p for p in path.strip("/").split("/") if not p.startswith("{")]
151+
prefix = path_parts[0] if path_parts else "root"
147152
else:
153+
# All path segments as prefix (default, backward compatible)
148154
path_parts = [p for p in path.strip("/").split("/") if not p.startswith("{")]
149155
prefix = ".".join(path_parts) if path_parts else "root"
150156

0 commit comments

Comments
 (0)