mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:31:58 +00:00
导入、同步起草撞上剖析、起草任务时,说法按这次要做的事说下一步
复核 R3。导入目录、同步起草撞上剖析任务回 409 profile_busy 时,原话来自 busy_message,结尾固定是「请等这次剖析结束 (或先停止它)再开始新的剖析」;使用者这时是在导入、起草,不是要开始新的剖析,导入对话框原样显示这句。导入撞上起草 任务时同样说「再开始新的起草」(这是改造前就有的,同一类问题一起改了)。 - catalog_profile_jobs.busy_message / ensure_free 加 then(缺省 THEN_PROFILE「再开始新的剖析」,建剖析任务、重试照旧)。 - catalog_jobs.busy_message / sync_busy_message / ensure_draft_free 同样加 then(缺省 THEN_DRAFT「再开始新的起草」); ensure_idle 和 _lane_busy 按这次要做的事(kind)选:导入撞上什么都是「再导入」;同步起草撞上剖析是「再起草」,撞上 起草仍是「再开始新的起草」(和建起草任务同一句话,test_catalog_draft_jobs 钉着这一点,没动那个文件)。 - 导入接口的说明补上撞上剖析任务回 profile_busy。 测试:剖析时导入、同步起草、再建剖析任务三句结尾各自对得上;队被剖析占着时(任务刚好在收尾)同样按这次要做的事说。
This commit is contained in:
@@ -346,8 +346,9 @@ async def preview_catalog_import(source_id: str, payload: CatalogImportIn,
|
||||
@router.post("/{source_id}/catalog/import")
|
||||
async def import_catalog(source_id: str, payload: CatalogImportIn, x_actor: str | None = Header(default=None),
|
||||
session: AsyncSession = Depends(get_session)) -> dict[str, Any]:
|
||||
"""导入:规则同预览,逐表写入(乐观锁),返回同样形状的报告和每张表写入后的版本。有排队或进行中的起草任务
|
||||
409 catalog_draft_busy;文件格式不对 422;没有表结构 409。"""
|
||||
"""导入:规则同预览,逐表写入(乐观锁),返回同样形状的报告和每张表写入后的版本。有排队或进行中的起草任务(或正在
|
||||
同步起草、导入)409 catalog_draft_busy,有剖析任务 409 profile_busy(说法的最后一句说「再导入」);文件格式不对 422;
|
||||
没有表结构 409。"""
|
||||
row, source = await _import_source(session, source_id)
|
||||
try:
|
||||
async with catalog_jobs.import_write(session, row):
|
||||
|
||||
@@ -313,12 +313,26 @@ async def last_drafts(session: AsyncSession, source_id: str) -> dict[str, dict[s
|
||||
# ==========================================================================
|
||||
|
||||
|
||||
def busy_message(row: Any) -> str:
|
||||
return f"数据源「{getattr(row, 'name', '')}」正在起草数据目录,请等这次起草结束(或先停止它)再开始新的起草"
|
||||
#: 撞上起草时说法的最后一句(「请等这次起草结束{then}」)缺省是建起草任务的;导入目录、同步起草撞上起草、剖析时按这次要做
|
||||
#: 的事说(_then)
|
||||
THEN_DRAFT = "再开始新的起草"
|
||||
|
||||
|
||||
def sync_busy_message(row: Any) -> str:
|
||||
return f"数据源「{getattr(row, 'name', '')}」正在起草数据目录(同步起草),请等这次起草结束再开始新的起草"
|
||||
def _then(kind: str, busy: str) -> str:
|
||||
"""同步写操作(kind:import / sync_draft)撞上 busy(draft 起草、profile 剖析)时说法的最后一句:导入撞上什么都是
|
||||
「再导入」;同步起草撞上起草是「再开始新的起草」(和建起草任务同一句话),撞上剖析是「再起草」——那时要做的是起草,
|
||||
不是「开始新的剖析」。"""
|
||||
if kind == "import":
|
||||
return "再导入"
|
||||
return THEN_DRAFT if busy == "draft" else "再起草"
|
||||
|
||||
|
||||
def busy_message(row: Any, then: str = THEN_DRAFT) -> str:
|
||||
return f"数据源「{getattr(row, 'name', '')}」正在起草数据目录,请等这次起草结束(或先停止它){then}"
|
||||
|
||||
|
||||
def sync_busy_message(row: Any, then: str = THEN_DRAFT) -> str:
|
||||
return f"数据源「{getattr(row, 'name', '')}」正在起草数据目录(同步起草),请等这次起草结束{then}"
|
||||
|
||||
|
||||
def import_busy_message(row: Any) -> str:
|
||||
@@ -334,33 +348,37 @@ def sync_drafting(source_id: str) -> bool:
|
||||
return source_id in _SYNC
|
||||
|
||||
|
||||
async def ensure_draft_free(session: AsyncSession, row: Any) -> None:
|
||||
async def ensure_draft_free(session: AsyncSession, row: Any, *, then: str = THEN_DRAFT) -> None:
|
||||
"""同种任务互斥:这个源没有在起草——没有排队或进行中的起草任务,也没有在同步起草。有就抛 DraftBusy。建起草任务、
|
||||
重试之前调它。剖析任务不算:同源有剖析任务时起草任务照建,排在它后面。"""
|
||||
重试之前调它。剖析任务不算:同源有剖析任务时起草任务照建,排在它后面。then 是说法最后一句要做的事。"""
|
||||
if row.id in _SYNC:
|
||||
raise DraftBusy(sync_busy_message(row))
|
||||
raise DraftBusy(sync_busy_message(row, then))
|
||||
if await active_job(session, row.id) is not None:
|
||||
raise DraftBusy(busy_message(row))
|
||||
raise DraftBusy(busy_message(row, then))
|
||||
|
||||
|
||||
async def ensure_idle(session: AsyncSession, row: Any) -> None:
|
||||
"""同步写操作(导入目录、同步起草)要求源空闲:没有在起草(DraftBusy,同 ensure_draft_free),也没有排队或进行中的
|
||||
剖析任务(catalog_profile_jobs.ProfileBusy,说法里写明进行到第几张)。"""
|
||||
async def ensure_idle(session: AsyncSession, row: Any, kind: str = "import") -> None:
|
||||
"""同步写操作(kind:import 导入目录、sync_draft 同步起草)要求源空闲:没有在起草(DraftBusy,同 ensure_draft_free),
|
||||
也没有排队或进行中的剖析任务(catalog_profile_jobs.ProfileBusy,说法里写明进行到第几张)。说法最后一句按 kind 说
|
||||
这次要做的事(_then)。"""
|
||||
from app.data import catalog_profile_jobs
|
||||
|
||||
await ensure_draft_free(session, row)
|
||||
await catalog_profile_jobs.ensure_free(session, row)
|
||||
await ensure_draft_free(session, row, then=_then(kind, "draft"))
|
||||
await catalog_profile_jobs.ensure_free(session, row, then=_then(kind, "profile"))
|
||||
|
||||
|
||||
def _lane_busy(row: Any, holder: runtime.LaneTicket) -> Exception:
|
||||
"""库里查过没有任务、这个源的队却被占着(同步起草、导入目录正在做,或者一个任务刚好在收尾):按占着的是谁说。"""
|
||||
def _lane_busy(row: Any, holder: runtime.LaneTicket, kind: str) -> Exception:
|
||||
"""库里查过没有任务、这个源的队却被占着(同步起草、导入目录正在做,或者一个任务刚好在收尾):按占着的是谁说,最后
|
||||
一句按这次要做的事(kind)说。"""
|
||||
if holder.kind == "profile":
|
||||
from app.data import catalog_profile_jobs
|
||||
|
||||
return catalog_profile_jobs.ProfileBusy(catalog_profile_jobs.busy_message(row, None))
|
||||
return catalog_profile_jobs.ProfileBusy(catalog_profile_jobs.busy_message(row, None,
|
||||
then=_then(kind, "profile")))
|
||||
if holder.kind == "import":
|
||||
return DraftBusy(import_busy_message(row))
|
||||
return DraftBusy(sync_busy_message(row) if holder.kind == "sync_draft" else busy_message(row))
|
||||
then = _then(kind, "draft")
|
||||
return DraftBusy(sync_busy_message(row, then) if holder.kind == "sync_draft" else busy_message(row, then))
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
@@ -371,7 +389,7 @@ async def _sync_write(row: Any, kind: str, check: Callable[[], Awaitable[None]])
|
||||
try:
|
||||
ticket = runtime.lanes.try_enter(row.id, kind)
|
||||
except runtime.LaneBusy as e:
|
||||
raise _lane_busy(row, e.holder) from e
|
||||
raise _lane_busy(row, e.holder, kind) from e
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
@@ -379,12 +397,12 @@ async def _sync_write(row: Any, kind: str, check: Callable[[], Awaitable[None]])
|
||||
|
||||
|
||||
async def _ensure_idle_but_sync(session: AsyncSession, row: Any) -> None:
|
||||
"""同 ensure_idle,只是不看 _SYNC:同步起草自己刚登记在里面。"""
|
||||
"""同 ensure_idle(…, "sync_draft"),只是不看 _SYNC:同步起草自己刚登记在里面。"""
|
||||
from app.data import catalog_profile_jobs
|
||||
|
||||
if await active_job(session, row.id) is not None:
|
||||
raise DraftBusy(busy_message(row))
|
||||
await catalog_profile_jobs.ensure_free(session, row)
|
||||
await catalog_profile_jobs.ensure_free(session, row, then=_then("sync_draft", "profile"))
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
@@ -406,7 +424,7 @@ async def sync_draft(session: AsyncSession, row: Any) -> AsyncIterator[None]:
|
||||
async def import_write(session: AsyncSession, row: Any) -> AsyncIterator[None]:
|
||||
"""导入目录期间占着这个源的队:源上有排队或进行中的起草、剖析任务(或者正在同步起草、导入)时抛 DraftBusy /
|
||||
ProfileBusy;导入期间新建的任务排在它后面。"""
|
||||
async with _sync_write(row, "import", lambda: ensure_idle(session, row)):
|
||||
async with _sync_write(row, "import", lambda: ensure_idle(session, row, "import")):
|
||||
yield
|
||||
|
||||
|
||||
@@ -791,7 +809,7 @@ manager = DraftJobs()
|
||||
|
||||
__all__ = ["ACTIVE", "BLANK_SAMPLE", "DraftBusy", "DraftFinished", "DraftJobs", "FINISHED", "GUIDANCE_MAX",
|
||||
"INTERRUPTED_ERROR", "INTERRUPTED_WRITTEN", "MAX_TABLES", "NOTE_MAX", "NothingToRetry", "RETRYABLE",
|
||||
"STOPPED_WRITTEN", "TASK_DONE", "TASK_STATUSES", "TextTooLong",
|
||||
"STOPPED_WRITTEN", "TASK_DONE", "TASK_STATUSES", "THEN_DRAFT", "TextTooLong",
|
||||
"active_job", "cancel", "check_model", "clean_text", "create_job", "ensure_draft_free", "ensure_idle",
|
||||
"guidance_of", "import_busy_message", "import_write", "job_brief", "job_dict", "job_out", "last_drafts",
|
||||
"latest_job", "load", "manager", "retry_tables", "save_guidance", "startup", "sync_busy_message", "sync_draft",
|
||||
|
||||
@@ -390,9 +390,15 @@ def due_reason(meta: dict[str, Any] | None, notes: dict[str, Any] | None, last:
|
||||
# ==========================================================================
|
||||
|
||||
|
||||
#: 撞上剖析任务时说法的最后一句(「请等这次剖析结束(或先停止它){then}」)缺省是建剖析任务的;导入目录、同步起草撞上
|
||||
#: 时各说各的(catalog_jobs):那时要做的是导入、起草,不是开始新的剖析
|
||||
THEN_PROFILE = "再开始新的剖析"
|
||||
|
||||
|
||||
def busy_message(row: Any, brief: dict[str, Any] | None, *, table: str | None = None,
|
||||
position: int | None = None) -> str:
|
||||
"""同源已有剖析任务时的说法:写明进行到第几张;从表详情点「剖析这张表」时再说这张表在不在那个任务里、排第几。"""
|
||||
position: int | None = None, then: str = THEN_PROFILE) -> str:
|
||||
"""同源已有剖析任务时的说法:写明进行到第几张;从表详情点「剖析这张表」时再说这张表在不在那个任务里、排第几;最后
|
||||
一句按调用方要做的事说(then)。"""
|
||||
name = getattr(row, "name", "") or ""
|
||||
if brief is None:
|
||||
text = f"数据源「{name}」正在剖析"
|
||||
@@ -402,11 +408,13 @@ def busy_message(row: Any, brief: dict[str, Any] | None, *, table: str | None =
|
||||
text = f"数据源「{name}」正在剖析:第 {min(brief['done'] + 1, brief['total'])} / {brief['total']} 张表"
|
||||
if table is not None:
|
||||
text += f",{table} 在这次剖析里,排第 {position} 张" if position else f",{table} 不在这次剖析里"
|
||||
return text + "。请等这次剖析结束(或先停止它)再开始新的剖析"
|
||||
return text + f"。请等这次剖析结束(或先停止它){then}"
|
||||
|
||||
|
||||
async def ensure_free(session: AsyncSession, row: Any, tables: list[str] | None = None) -> None:
|
||||
"""这个源没有排队或进行中的剖析任务;有就抛 ProfileBusy。只指定了一张表时,说法里带上这张表在不在那个任务里。"""
|
||||
async def ensure_free(session: AsyncSession, row: Any, tables: list[str] | None = None, *,
|
||||
then: str = THEN_PROFILE) -> None:
|
||||
"""这个源没有排队或进行中的剖析任务;有就抛 ProfileBusy。只指定了一张表时,说法里带上这张表在不在那个任务里;then 是
|
||||
说法的最后一句要做的事(见 busy_message)。"""
|
||||
job = await active_job(session, row.id)
|
||||
if job is None:
|
||||
return
|
||||
@@ -417,7 +425,7 @@ async def ensure_free(session: AsyncSession, row: Any, tables: list[str] | None
|
||||
table = key
|
||||
names = [r.table_name for r in await task_rows(session, job.id)]
|
||||
position = names.index(key) + 1 if key in names else None
|
||||
raise ProfileBusy(busy_message(row, brief, table=table, position=position))
|
||||
raise ProfileBusy(busy_message(row, brief, table=table, position=position, then=then))
|
||||
|
||||
|
||||
#: 挑出 0 张表时的说法
|
||||
@@ -876,7 +884,7 @@ manager = ProfileJobs()
|
||||
|
||||
|
||||
__all__ = ["INTERRUPTED_ERROR", "KEEP_JOBS", "MAX_TABLES", "NOTHING_AT_ALL", "NOTHING_NEEDED", "NothingToRetry",
|
||||
"ProfileBusy", "ProfileFinished", "ProfileJobs", "ProfileNothing", "active_job", "busy_message", "cancel",
|
||||
"create_job", "due_reason", "ensure_free", "job_brief", "job_dict", "job_out", "last_detail",
|
||||
"last_profiles", "latest_job", "load", "load_task", "manager", "prune", "public_last", "retry_tables",
|
||||
"startup", "task_detail", "task_row", "task_rows", "unsaved_error", "waiting_for"]
|
||||
"ProfileBusy", "ProfileFinished", "ProfileJobs", "ProfileNothing", "THEN_PROFILE", "active_job",
|
||||
"busy_message", "cancel", "create_job", "due_reason", "ensure_free", "job_brief", "job_dict", "job_out",
|
||||
"last_detail", "last_profiles", "latest_job", "load", "load_task", "manager", "prune", "public_last",
|
||||
"retry_tables", "startup", "task_detail", "task_row", "task_rows", "unsaved_error", "waiting_for"]
|
||||
|
||||
@@ -186,11 +186,20 @@ async def test_import_and_sync_draft_are_409_while_profiling(client, make_source
|
||||
job = (await _start(client, sid, ["channels"])).json()["job"]
|
||||
await asyncio.wait_for(gate.entered.wait(), 10)
|
||||
|
||||
# 说法按这次要做的事说下一步:是在导入、起草,不是要开始新的剖析
|
||||
r = await client.post(f"{_base(sid)}/import", json={"content": exported})
|
||||
assert r.status_code == 409 and r.json()["code"] == "profile_busy", r.text
|
||||
assert "正在剖析" in r.json()["detail"]
|
||||
assert "正在剖析" in r.json()["detail"] and r.json()["detail"].endswith("请等这次剖析结束(或先停止它)再导入")
|
||||
r = await client.post(f"{_base(sid)}/draft", json={"tables": ["gates"]})
|
||||
assert r.status_code == 409 and r.json()["code"] == "profile_busy", r.text
|
||||
assert r.json()["detail"].endswith("请等这次剖析结束(或先停止它)再起草")
|
||||
again = await _start(client, sid, ["gates"])
|
||||
assert again.json()["detail"].endswith("请等这次剖析结束(或先停止它)再开始新的剖析")
|
||||
# 库里查过没有任务、队却被剖析占着(任务刚好在收尾)时同样按这次要做的事说
|
||||
row, holder = SimpleNamespace(name="x"), SimpleNamespace(kind="profile")
|
||||
assert str(catalog_jobs._lane_busy(row, holder, "import")).endswith("(或先停止它)再导入")
|
||||
assert str(catalog_jobs._lane_busy(row, holder, "sync_draft")).endswith("(或先停止它)再起草")
|
||||
assert str(catalog_jobs._lane_busy(row, SimpleNamespace(kind="draft"), "import")).endswith("(或先停止它)再导入")
|
||||
|
||||
gate.release.set()
|
||||
await catalog_profile_jobs.manager.wait(job["id"], timeout=10)
|
||||
|
||||
Reference in New Issue
Block a user