Skip to content

Fix CLI descriptions lost under python -OO by falling back to json_schema_extra#843

Merged
hramezani merged 4 commits intomainfrom
fix/cli-description-json-schema-extra-fallback
Apr 20, 2026
Merged

Fix CLI descriptions lost under python -OO by falling back to json_schema_extra#843
hramezani merged 4 commits intomainfrom
fix/cli-description-json-schema-extra-fallback

Conversation

@hramezani
Copy link
Copy Markdown
Member

Summary

  • When running with python -OO, docstrings are stripped (__doc__ is None), causing CLI help descriptions to disappear entirely
  • Added _get_model_description() helper that falls back to json_schema_extra['description'] from model_config when __doc__ is None
  • Applies to all 4 places __doc__ was used: root parser, subcommand description, subcommand help with cli_use_class_docs_for_groups, and model group description

Fixes #827

Test plan

  • Unit tests for _get_model_description covering: docstring present, no doc/no extra, dict fallback, docstring precedence, callable json_schema_extra, pydantic dataclass, BaseSettings
  • Integration tests for CLI help output: root parser fallback, docstring precedence, cli_use_class_docs_for_groups fallback, subcommand fallback
  • All 606 existing tests pass

🤖 Generated with Claude Code

@hramezani hramezani force-pushed the fix/cli-description-json-schema-extra-fallback branch from dbaa702 to 1a10995 Compare April 8, 2026 11:40
…_schema_extra`

When running with `python -OO`, docstrings are stripped (`__doc__` is `None`),
causing CLI help descriptions to disappear. This adds a fallback to
`json_schema_extra['description']` from model_config, matching pydantic's
behavior for JSON schema generation.

Fixes #827

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hramezani hramezani force-pushed the fix/cli-description-json-schema-extra-fallback branch from 1a10995 to d4c4e3d Compare April 8, 2026 12:07
Copy link
Copy Markdown

@aytekinar aytekinar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this type of fix, @hramezani?

Follow-up: #827 (comment)

Comment on lines +100 to +107
if model_cls.__doc__ is not None:
return dedent(model_cls.__doc__)
config: Any = {}
if is_model_class(model_cls):
config = model_cls.model_config
elif is_pydantic_dataclass(model_cls):
config = getattr(model_cls, '__pydantic_config__', None) or {}
json_schema_extra = config.get('json_schema_extra')
Copy link
Copy Markdown

@aytekinar aytekinar Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if model_cls.__doc__ is not None:
return dedent(model_cls.__doc__)
config: Any = {}
if is_model_class(model_cls):
config = model_cls.model_config
elif is_pydantic_dataclass(model_cls):
config = getattr(model_cls, '__pydantic_config__', None) or {}
json_schema_extra = config.get('json_schema_extra')
config: Any = {}
if is_model_class(model_cls):
config = model_cls.model_config
elif is_pydantic_dataclass(model_cls):
config = getattr(model_cls, '__pydantic_config__', {})
json_schema_extra = config.get('json_schema_extra', model_cls.__doc__)
if isinstance(json_schema_extra, str):
return dedent(json_schema_extra)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be a better / more symmetric fix when the current functionality of pydantic is considered.

Comment thread tests/test_source_cli.py Outdated
Comment on lines +3498 to +3504
def test_get_model_description_docstring_takes_precedence():
class MyModel(BaseModel):
"""Docstring wins."""

model_config = ConfigDict(json_schema_extra={'description': 'Schema description.'})

assert _get_model_description(MyModel) == 'Docstring wins.'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_get_model_description_docstring_takes_precedence():
class MyModel(BaseModel):
"""Docstring wins."""
model_config = ConfigDict(json_schema_extra={'description': 'Schema description.'})
assert _get_model_description(MyModel) == 'Docstring wins.'
def test_get_model_description_schema_takes_precedence():
class MyModel(BaseModel):
"""Docstring description."""
model_config = ConfigDict(json_schema_extra={'description': 'Schema wins.'})
assert _get_model_description(MyModel) == 'Schema wins.'

Comment thread tests/test_source_cli.py Outdated
Comment on lines +3568 to +3596
def test_cli_docstring_takes_precedence_over_json_schema_extra(capsys, monkeypatch):
"""When __doc__ is available, it should be used instead of json_schema_extra description."""

class Settings(BaseSettings, cli_prog_name='example.py'):
"""Docstring description."""

model_config = SettingsConfigDict(
cli_parse_args=True,
json_schema_extra={'description': 'JSON schema description.'},
)
my_var: str = 'default'

with monkeypatch.context() as m:
m.setattr(sys, 'argv', ['example.py', '--help'])

with pytest.raises(SystemExit):
Settings()

assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--my_var str]

Docstring description.

{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
--my_var str (default: default)
"""
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_cli_docstring_takes_precedence_over_json_schema_extra(capsys, monkeypatch):
"""When __doc__ is available, it should be used instead of json_schema_extra description."""
class Settings(BaseSettings, cli_prog_name='example.py'):
"""Docstring description."""
model_config = SettingsConfigDict(
cli_parse_args=True,
json_schema_extra={'description': 'JSON schema description.'},
)
my_var: str = 'default'
with monkeypatch.context() as m:
m.setattr(sys, 'argv', ['example.py', '--help'])
with pytest.raises(SystemExit):
Settings()
assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--my_var str]
Docstring description.
{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
--my_var str (default: default)
"""
)
def test_cli_json_schema_extra_takes_precedence_over_docstring(capsys, monkeypatch):
"""Even when __doc__ is available, json_schema_extra description should be used, instead."""
class Settings(BaseSettings, cli_prog_name='example.py'):
"""Docstring description."""
model_config = SettingsConfigDict(
cli_parse_args=True,
json_schema_extra={'description': 'JSON schema description.'},
)
my_var: str = 'default'
with monkeypatch.context() as m:
m.setattr(sys, 'argv', ['example.py', '--help'])
with pytest.raises(SystemExit):
Settings()
assert (
capsys.readouterr().out
== f"""usage: example.py [-h] [--my_var str]
JSON schema description.
{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
--my_var str (default: default)
"""
)

hramezani and others added 3 commits April 19, 2026 13:52
…ydantic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tests for callable json_schema_extra on dataclass and dict without
description key falling back to docstring. Remove unreachable else branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hramezani hramezani merged commit 39e551c into main Apr 20, 2026
19 checks passed
@hramezani hramezani deleted the fix/cli-description-json-schema-extra-fallback branch April 20, 2026 13:01
@hramezani hramezani mentioned this pull request Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI descriptions are lost in optimized Python runs

2 participants