refactor(prompts): one renderer for Python signatures

`extract_py_functions` inlined a third copy, one clause behind the two functions
that already do this — it lost the bare `*` until last commit, and still dropped
positional-only arguments and annotated varargs. It calls the shared one; the
generated files come out byte-identical.

The picker says which of the three refusals it hit, including the database it
could not reach, and an app that keeps no data table no longer creates a schema
in one: the app would never refer to it, and where the database is unreachable
the failure is one the user cannot act on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5arH3G2Sa1Qqm32veJQ1n
This commit is contained in:
Diego Imbert
2026-09-04 17:09:35 +02:00
co-authored by Claude Opus 5
parent 8a40ff46fd
commit 7cd6e081a7
2 changed files with 14 additions and 45 deletions
@@ -248,7 +248,10 @@
async function start(withPrompt: boolean) {
const template = templates[selectedTemplateIndex]
if (schemaMode === 'new' && newSchemaName && selectedDatatable && opWs) {
// Only for an app that keeps the data table: with creation off nothing names
// it, so making a schema in it is work the app will never refer to — and on
// a database that could not be read, a failure the user cannot act on.
if (tableCreationEnabled && schemaMode === 'new' && newSchemaName && selectedDatatable && opWs) {
try {
const { dbSchemaOpsWithPreviewScripts } = await import('$lib/components/dbOps')
const dbOps = dbSchemaOpsWithPreviewScripts({
@@ -379,9 +382,13 @@
size="sm"
class="w-40"
/>
{#if noUsableRole || rolesUnknown}
{#if noUsableRole || rolesUnknown || accessUnknown}
<span class="text-xs text-red-600 dark:text-red-400">
{rolesUnknown ? 'could not read its roles' : 'no role you can use'}
{rolesUnknown
? 'could not read its roles'
: accessUnknown
? 'could not reach it'
: 'no role you can use'}
</span>
{/if}
{#if showRolePicker}
+4 -42
View File
@@ -198,47 +198,9 @@ def extract_py_functions(content: str) -> list[dict]:
if '.. deprecated::' in docstring:
return
# Build parameter list
params = []
args = node.args
# Handle regular args
num_defaults = len(args.defaults)
num_args = len(args.args)
for i, arg in enumerate(args.args):
if arg.arg == 'self':
continue
param_str = arg.arg
if arg.annotation:
param_str += f": {ast.unparse(arg.annotation)}"
# Check if has default
default_idx = i - (num_args - num_defaults)
if default_idx >= 0:
default = args.defaults[default_idx]
param_str += f" = {ast.unparse(default)}"
params.append(param_str)
# Handle *args
if args.vararg:
params.append(f"*{args.vararg.arg}")
elif args.kwonlyargs:
# The bare separator is part of the signature: without it the advertised
# call is positional, and an agent following it gets a TypeError.
params.append('*')
# Handle keyword-only args
for i, arg in enumerate(args.kwonlyargs):
param_str = arg.arg
if arg.annotation:
param_str += f": {ast.unparse(arg.annotation)}"
if args.kw_defaults[i]:
param_str += f" = {ast.unparse(args.kw_defaults[i])}"
params.append(param_str)
# Handle **kwargs
if args.kwarg:
params.append(f"**{args.kwarg.arg}")
# One renderer for Python signatures — see `_format_py_params`, which also
# keeps the bare `*`, the positional-only `/` and annotated varargs.
params_str = _format_py_params(node, skip_self=True)
# Get return type
return_type = ''
@@ -248,7 +210,7 @@ def extract_py_functions(content: str) -> list[dict]:
seen_names.add(node.name)
functions.append({
'name': node.name,
'params': ', '.join(params),
'params': params_str,
'return_type': return_type,
'docstring': docstring,
'async': isinstance(node, ast.AsyncFunctionDef)