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.
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
|
||||
|
||||
Reference in New Issue
Block a user