Backend messages and field descriptions that reach the UI lose their em-dashes
Per the Microcopy rule: size and duration errors, the flavor errors, run parameter errors, the Resources field descriptions, 401/409/400 details, secret errors, the websocket node description and its on_connect setting. The generated client follows. Claude-Session: https://claude.ai/code/session_01SSi5AvZ7pFmySK8zy3NPMh
This commit is contained in:
@@ -527,7 +527,7 @@ def get_current_user(request: Request, session: SessionDep, token: TokenDep) ->
|
||||
if user is None or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="This session is no longer valid — please log in again",
|
||||
detail="This session is no longer valid. Please log in again",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ async def discard_dashboard_draft(name: str, store: DashboardStoreDep) -> Any:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
f"Dashboard '{name}' has never been published — delete it instead "
|
||||
f"Dashboard '{name}' has never been published. Delete it instead "
|
||||
"of discarding it"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -303,7 +303,7 @@ def _require_enabled(controller: FlowController, name: str) -> None:
|
||||
if not controller.is_enabled(name):
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"Flow '{name}' is stopped — start it before running it",
|
||||
detail=f"Flow '{name}' is stopped. Start it before running it",
|
||||
)
|
||||
|
||||
|
||||
@@ -601,7 +601,7 @@ async def discard_draft(name: str, controller: FlowControllerDep) -> Any:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
f"Flow '{name}' has never been published — delete it instead of "
|
||||
f"Flow '{name}' has never been published. Delete it instead of "
|
||||
"discarding it"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -358,7 +358,7 @@ NODE_TYPES: dict[str, NodeType] = {
|
||||
),
|
||||
"websocket": NodeType(
|
||||
title="WebSocket",
|
||||
description="Listen on a socket, or send to it — as a client or a server.",
|
||||
description="Listen on a socket, or send to it, as a client or a server.",
|
||||
cls=WebSocketNode,
|
||||
params_schema=_schema_of(WebSocketNode),
|
||||
),
|
||||
@@ -2133,8 +2133,8 @@ def _collect_issues(
|
||||
ValidationIssue(
|
||||
code="unauthenticated_hook",
|
||||
message=(
|
||||
f"Hook '{entry.node.local_id}' has no secret — "
|
||||
"anyone who knows its URL can reach it."
|
||||
f"Hook '{entry.node.local_id}' has no secret. "
|
||||
"Anyone who knows its URL can reach it."
|
||||
),
|
||||
flow=entry.flow,
|
||||
node=entry.id,
|
||||
|
||||
@@ -256,7 +256,7 @@ class MessageSpec(BaseModel):
|
||||
raise TypeError(
|
||||
f"{where}: {stray} cannot travel as JSON. A subset with nothing "
|
||||
"in it, or a division that had no denominator, is what usually "
|
||||
"produces one — publish None, or a number that says so."
|
||||
"produces one. Publish None, or a number that says so."
|
||||
)
|
||||
if self.dtype is DType.SERIES:
|
||||
ok = _is_series(value)
|
||||
|
||||
@@ -91,7 +91,7 @@ class WebSocketNode(Node):
|
||||
on_connect: list[Any] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Frames sent after every (re)connect, client only — the "
|
||||
"Frames sent after every (re)connect, client only: the "
|
||||
"subscribe or auth message many APIs expect. A string goes "
|
||||
"raw, anything else as JSON; an item may be a secret reference."
|
||||
),
|
||||
|
||||
@@ -299,7 +299,7 @@ def resolve_flavor(wanted: Resources) -> Resources:
|
||||
row = session.get(Flavor, wanted.flavor)
|
||||
if row is None:
|
||||
raise UnknownFlavor(
|
||||
f"flavor '{wanted.flavor}' does not exist — `fluksio flavors` lists them"
|
||||
f"flavor '{wanted.flavor}' does not exist. `fluksio flavors` lists them"
|
||||
)
|
||||
# Built fresh rather than copied, so the flavor-and-numbers check runs on
|
||||
# the result and this cannot quietly produce something invalid.
|
||||
|
||||
@@ -282,8 +282,8 @@ def _from_run(
|
||||
run_id, _, output = spelling.partition(".")
|
||||
if not run_id or not output:
|
||||
raise RunRejected(
|
||||
f"Parameter '{key}': '{RUN_REF_PREFIX}{spelling}' names no output — "
|
||||
f"write {RUN_REF_PREFIX}<run id>.<output>"
|
||||
f"Parameter '{key}': '{RUN_REF_PREFIX}{spelling}' names no output. "
|
||||
f"Write {RUN_REF_PREFIX}<run id>.<output>"
|
||||
)
|
||||
run = session.get(Run, run_id)
|
||||
if run is None:
|
||||
|
||||
@@ -38,7 +38,7 @@ def _megabytes(value: Any) -> Any:
|
||||
return value
|
||||
match = _SIZE.match(value)
|
||||
if not match:
|
||||
raise ValueError(f"'{value}' is not a size — write 512M, 2G, or 512")
|
||||
raise ValueError(f"'{value}' is not a size. Write 512M, 2G, or 512")
|
||||
amount, unit = int(match.group(1)), (match.group(2) or "m").lower()
|
||||
return amount * 1024 if unit.startswith("g") else amount
|
||||
|
||||
@@ -49,7 +49,7 @@ def _seconds(value: Any) -> Any:
|
||||
return value
|
||||
match = _SPAN.match(value)
|
||||
if not match:
|
||||
raise ValueError(f"'{value}' is not a duration — write 90s, 30m, 2h, or 90")
|
||||
raise ValueError(f"'{value}' is not a duration. Write 90s, 30m, 2h, or 90")
|
||||
return int(match.group(1)) * _SPAN_SECONDS[(match.group(2) or "s").lower()]
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class Resources(BaseModel):
|
||||
description=(
|
||||
"Megabytes held for the whole execution; accepts '512M' or '2G'. "
|
||||
"Counted against machines that said how much they have, and "
|
||||
"ignored by those that did not — which is a machine with nothing "
|
||||
"ignored by those that did not, which is a machine with nothing "
|
||||
"to say about memory, not one with none."
|
||||
),
|
||||
)
|
||||
@@ -113,15 +113,15 @@ class Resources(BaseModel):
|
||||
description=(
|
||||
"How long one execution is expected to take; accepts '30m' or "
|
||||
"'2h'. A statement about the node for whoever is planning around "
|
||||
"it, not a limit — the limit is `timeout`."
|
||||
"it, not a limit. The limit is `timeout`."
|
||||
),
|
||||
)
|
||||
env: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
description=(
|
||||
"Extra environment for the worker this node runs in, applied over "
|
||||
"what the allocation derives. Where a library's own tuning goes — "
|
||||
"XLA_FLAGS, XLA_PYTHON_CLIENT_MEM_FRACTION — since those are "
|
||||
"what the allocation derives. Where a library's own tuning goes "
|
||||
"(XLA_FLAGS, XLA_PYTHON_CLIENT_MEM_FRACTION), since those are "
|
||||
"composed strings the engine must not invent."
|
||||
),
|
||||
)
|
||||
@@ -140,8 +140,8 @@ class Resources(BaseModel):
|
||||
"""
|
||||
if self.flavor and (self.cpus != 1 or self.gpus != 0 or self.ram is not None):
|
||||
raise ValueError(
|
||||
f"flavor '{self.flavor}' already says how much — "
|
||||
"drop cpus, gpus and ram, or drop the flavor"
|
||||
f"flavor '{self.flavor}' already says how much. "
|
||||
"Drop cpus, gpus and ram, or drop the flavor"
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -203,8 +203,8 @@ class NodeDef(BaseModel):
|
||||
resources: Resources | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"What one execution of this node holds while it runs. Absent — the "
|
||||
"default — means it is not accounted for and shares the engine's "
|
||||
"What one execution of this node holds while it runs. Absent (the "
|
||||
"default) means it is not accounted for and shares the engine's "
|
||||
"workers, which is right for everything that is not compute-heavy."
|
||||
),
|
||||
)
|
||||
@@ -222,7 +222,7 @@ class NodeDef(BaseModel):
|
||||
description=(
|
||||
"What those modules hashed to at sync. The engine hashes them "
|
||||
"again when a run is claimed, and falls back to this when it "
|
||||
"cannot see the files — a worker on another machine."
|
||||
"cannot see the files, as a worker on another machine does."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class SecretNotFound(KeyError):
|
||||
self.name = name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"No secret named '{self.name}' — add it under Secrets."
|
||||
return f"No secret named '{self.name}'. Add it under Secrets."
|
||||
|
||||
|
||||
def fernet(key: str) -> Fernet:
|
||||
@@ -60,7 +60,7 @@ class SecretsStore:
|
||||
# flows coming up with silently missing credentials are far harder
|
||||
# to diagnose than a node that reports why it did not load.
|
||||
raise SecretsUnreadable(
|
||||
f"Cannot decrypt {self._path} — it was written with a "
|
||||
f"Cannot decrypt {self._path}. It was written with a "
|
||||
"different SECRET_KEY. Re-encrypt it with "
|
||||
"scripts/rotate_secret_key.py."
|
||||
) from exc
|
||||
|
||||
@@ -2764,7 +2764,7 @@ export const NodeDef_InputSchema = {
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
description: "What one execution of this node holds while it runs. Absent — the default — means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy."
|
||||
description: "What one execution of this node holds while it runs. Absent (the default) means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy."
|
||||
},
|
||||
code_files: {
|
||||
additionalProperties: {
|
||||
@@ -2777,7 +2777,7 @@ export const NodeDef_InputSchema = {
|
||||
code_digest: {
|
||||
type: 'string',
|
||||
title: 'Code Digest',
|
||||
description: 'What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files — a worker on another machine.',
|
||||
description: 'What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files, as a worker on another machine does.',
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
@@ -2884,7 +2884,7 @@ export const NodeDef_OutputSchema = {
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
description: "What one execution of this node holds while it runs. Absent — the default — means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy."
|
||||
description: "What one execution of this node holds while it runs. Absent (the default) means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy."
|
||||
},
|
||||
code_files: {
|
||||
additionalProperties: {
|
||||
@@ -2897,7 +2897,7 @@ export const NodeDef_OutputSchema = {
|
||||
code_digest: {
|
||||
type: 'string',
|
||||
title: 'Code Digest',
|
||||
description: 'What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files — a worker on another machine.',
|
||||
description: 'What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files, as a worker on another machine does.',
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
@@ -3693,7 +3693,7 @@ export const ResourcesSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Ram',
|
||||
description: "Megabytes held for the whole execution; accepts '512M' or '2G'. Counted against machines that said how much they have, and ignored by those that did not — which is a machine with nothing to say about memory, not one with none."
|
||||
description: "Megabytes held for the whole execution; accepts '512M' or '2G'. Counted against machines that said how much they have, and ignored by those that did not, which is a machine with nothing to say about memory, not one with none."
|
||||
},
|
||||
flavor: {
|
||||
anyOf: [
|
||||
@@ -3718,7 +3718,7 @@ export const ResourcesSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Duration S',
|
||||
description: "How long one execution is expected to take; accepts '30m' or '2h'. A statement about the node for whoever is planning around it, not a limit — the limit is `timeout`."
|
||||
description: "How long one execution is expected to take; accepts '30m' or '2h'. A statement about the node for whoever is planning around it, not a limit. The limit is `timeout`."
|
||||
},
|
||||
env: {
|
||||
additionalProperties: {
|
||||
@@ -3726,7 +3726,7 @@ export const ResourcesSchema = {
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Env',
|
||||
description: "Extra environment for the worker this node runs in, applied over what the allocation derives. Where a library's own tuning goes — XLA_FLAGS, XLA_PYTHON_CLIENT_MEM_FRACTION — since those are composed strings the engine must not invent."
|
||||
description: "Extra environment for the worker this node runs in, applied over what the allocation derives. Where a library's own tuning goes (XLA_FLAGS, XLA_PYTHON_CLIENT_MEM_FRACTION), since those are composed strings the engine must not invent."
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
|
||||
@@ -928,7 +928,7 @@ export type NodeDef_Input = {
|
||||
*/
|
||||
cache?: boolean;
|
||||
/**
|
||||
* What one execution of this node holds while it runs. Absent — the default — means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy.
|
||||
* What one execution of this node holds while it runs. Absent (the default) means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy.
|
||||
*/
|
||||
resources?: (Resources | null);
|
||||
/**
|
||||
@@ -938,7 +938,7 @@ export type NodeDef_Input = {
|
||||
[key: string]: (string);
|
||||
};
|
||||
/**
|
||||
* What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files — a worker on another machine.
|
||||
* What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files, as a worker on another machine does.
|
||||
*/
|
||||
code_digest?: string;
|
||||
};
|
||||
@@ -982,7 +982,7 @@ export type NodeDef_Output = {
|
||||
*/
|
||||
cache?: boolean;
|
||||
/**
|
||||
* What one execution of this node holds while it runs. Absent — the default — means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy.
|
||||
* What one execution of this node holds while it runs. Absent (the default) means it is not accounted for and shares the engine's workers, which is right for everything that is not compute-heavy.
|
||||
*/
|
||||
resources?: (Resources | null);
|
||||
/**
|
||||
@@ -992,7 +992,7 @@ export type NodeDef_Output = {
|
||||
[key: string]: (string);
|
||||
};
|
||||
/**
|
||||
* What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files — a worker on another machine.
|
||||
* What those modules hashed to at sync. The engine hashes them again when a run is claimed, and falls back to this when it cannot see the files, as a worker on another machine does.
|
||||
*/
|
||||
code_digest?: string;
|
||||
};
|
||||
@@ -1232,7 +1232,7 @@ export type Resources = {
|
||||
*/
|
||||
gpus?: number;
|
||||
/**
|
||||
* Megabytes held for the whole execution; accepts '512M' or '2G'. Counted against machines that said how much they have, and ignored by those that did not — which is a machine with nothing to say about memory, not one with none.
|
||||
* Megabytes held for the whole execution; accepts '512M' or '2G'. Counted against machines that said how much they have, and ignored by those that did not, which is a machine with nothing to say about memory, not one with none.
|
||||
*/
|
||||
ram?: (number | null);
|
||||
/**
|
||||
@@ -1240,11 +1240,11 @@ export type Resources = {
|
||||
*/
|
||||
flavor?: (string | null);
|
||||
/**
|
||||
* How long one execution is expected to take; accepts '30m' or '2h'. A statement about the node for whoever is planning around it, not a limit — the limit is `timeout`.
|
||||
* How long one execution is expected to take; accepts '30m' or '2h'. A statement about the node for whoever is planning around it, not a limit. The limit is `timeout`.
|
||||
*/
|
||||
duration_s?: (number | null);
|
||||
/**
|
||||
* Extra environment for the worker this node runs in, applied over what the allocation derives. Where a library's own tuning goes — XLA_FLAGS, XLA_PYTHON_CLIENT_MEM_FRACTION — since those are composed strings the engine must not invent.
|
||||
* Extra environment for the worker this node runs in, applied over what the allocation derives. Where a library's own tuning goes (XLA_FLAGS, XLA_PYTHON_CLIENT_MEM_FRACTION), since those are composed strings the engine must not invent.
|
||||
*/
|
||||
env?: {
|
||||
[key: string]: (string);
|
||||
|
||||
Reference in New Issue
Block a user