Clear import directory, possibly

This commit is contained in:
sebastien committed 2026-10-06 11:26:03 +02:00
1 parent 52a8589261
commit 22c04f6e15
7 files changed
+88 -6

No files matched your search

+9 -2
View File
@@ -30,6 +30,7 @@ from copienator import (
workspace_from_args, workspace_from_args,
) )
from copienator.annotation_data import load_annotation_data from copienator.annotation_data import load_annotation_data
from copienator.commands.import_annotations import clear_import_directory
from copienator.answer_info import build_answer_info from copienator.answer_info import build_answer_info
from copienator.filesystem import staged_directory from copienator.filesystem import staged_directory
from copienator.feedback_boxes import valid_feedback_box from copienator.feedback_boxes import valid_feedback_box
@@ -525,6 +526,8 @@ def process_correction(root_dir, data, all_labels, overwrite=False):
def build_parser() -> argparse.ArgumentParser: def build_parser() -> argparse.ArgumentParser:
parser = evaluation_parser("Generate simple annotated copies.") parser = evaluation_parser("Generate simple annotated copies.")
parser.add_argument("--clear-import", action="store_true",
help="Empty IMPORT_DIR after successful annotation generation")
parser.add_argument( parser.add_argument(
"--overwrite", "--overwrite",
action="store_true", action="store_true",
@@ -533,7 +536,8 @@ def build_parser() -> argparse.ArgumentParser:
return parser return parser
def run(workspace: EvaluationWorkspace, *, overwrite: bool = False) -> ExitCode: def run(workspace: EvaluationWorkspace, *, overwrite: bool = False,
clear_import: bool = False) -> ExitCode:
workspace.require_files("labels", "correction.json") workspace.require_files("labels", "correction.json")
workspace.require_directories("Copies", "Par label") workspace.require_directories("Copies", "Par label")
labels = utils.read_all_labels(workspace.root) labels = utils.read_all_labels(workspace.root)
@@ -551,6 +555,8 @@ def run(workspace: EvaluationWorkspace, *, overwrite: bool = False) -> ExitCode:
) )
if loaded.warnings and result == ExitCode.SUCCESS: if loaded.warnings and result == ExitCode.SUCCESS:
return ExitCode.PARTIAL return ExitCode.PARTIAL
if clear_import and result == ExitCode.SUCCESS:
clear_import_directory(workspace)
return result return result
@@ -559,7 +565,8 @@ def main(argv: Sequence[str] | None = None) -> int:
return execute( return execute(
parser, parser,
argv, argv,
lambda args: run(workspace_from_args(args), overwrite=args.overwrite), lambda args: run(workspace_from_args(args), overwrite=args.overwrite,
clear_import=args.clear_import),
) )
+12 -3
View File
@@ -24,6 +24,7 @@ from copienator import (
) )
from copienator.annotation_data import load_annotation_data from copienator.annotation_data import load_annotation_data
from copienator.commands import annotating, annotating_with_checks from copienator.commands import annotating, annotating_with_checks
from copienator.commands.import_annotations import clear_import_directory
from copienator.filesystem import staged_directory from copienator.filesystem import staged_directory
from copienator.utils import natural_key from copienator.utils import natural_key
@@ -347,7 +348,8 @@ def _generate_groups(
def run( def run(
workspace: EvaluationWorkspace, *, overwrite: bool = False, refaire: bool = False workspace: EvaluationWorkspace, *, overwrite: bool = False, refaire: bool = False,
clear_import: bool = False,
) -> ExitCode: ) -> ExitCode:
workspace.require_files("labels", "correction.json") workspace.require_files("labels", "correction.json")
workspace.require_directories("Copies", "Par label") workspace.require_directories("Copies", "Par label")
@@ -408,11 +410,17 @@ def run(
return ExitCode.PARTIAL return ExitCode.PARTIAL
if generated == 0: if generated == 0:
print("No new grouped annotations were required.") print("No new grouped annotations were required.")
return ExitCode.PARTIAL if loaded.warnings else ExitCode.SUCCESS if loaded.warnings:
return ExitCode.PARTIAL
if clear_import:
clear_import_directory(workspace)
return ExitCode.SUCCESS
def build_parser() -> argparse.ArgumentParser: def build_parser() -> argparse.ArgumentParser:
parser = evaluation_parser("Generate annotated PDFs grouped by labels.") parser = evaluation_parser("Generate annotated PDFs grouped by labels.")
parser.add_argument("--clear-import", action="store_true",
help="Empty IMPORT_DIR after successful annotation generation")
parser.add_argument( parser.add_argument(
"--overwrite", action="store_true", help="Replace annotation outputs safely" "--overwrite", action="store_true", help="Replace annotation outputs safely"
) )
@@ -430,7 +438,8 @@ def main(argv: Sequence[str] | None = None) -> int:
parser, parser,
argv, argv,
lambda args: run( lambda args: run(
workspace_from_args(args), overwrite=args.overwrite, refaire=args.refaire workspace_from_args(args), overwrite=args.overwrite, refaire=args.refaire,
clear_import=args.clear_import,
), ),
) )
@@ -27,6 +27,7 @@ from copienator import (
) )
from copienator.annotation_data import load_annotation_data from copienator.annotation_data import load_annotation_data
from copienator.commands import annotating from copienator.commands import annotating
from copienator.commands.import_annotations import clear_import_directory
from copienator.filesystem import staged_directory from copienator.filesystem import staged_directory
from copienator.utils import natural_key from copienator.utils import natural_key
@@ -269,6 +270,7 @@ def run(
*, *,
overwrite: bool = False, overwrite: bool = False,
refaire: bool = False, refaire: bool = False,
clear_import: bool = False,
) -> ExitCode: ) -> ExitCode:
workspace.require_files("labels", "correction.json") workspace.require_files("labels", "correction.json")
workspace.require_directories("Copies", "Par label") workspace.require_directories("Copies", "Par label")
@@ -319,6 +321,8 @@ def run(
except IncompleteRedo: except IncompleteRedo:
print("Warning: incomplete redo generation; previous BRnot was preserved.") print("Warning: incomplete redo generation; previous BRnot was preserved.")
return ExitCode.PARTIAL return ExitCode.PARTIAL
if clear_import:
clear_import_directory(workspace)
return ExitCode.SUCCESS return ExitCode.SUCCESS
statuses: list[str] = [] statuses: list[str] = []
@@ -338,11 +342,15 @@ def run(
statuses.append(future.result()) statuses.append(future.result())
if loaded.warnings or "partial" in statuses: if loaded.warnings or "partial" in statuses:
return ExitCode.PARTIAL return ExitCode.PARTIAL
if clear_import:
clear_import_directory(workspace)
return ExitCode.SUCCESS return ExitCode.SUCCESS
def build_parser() -> argparse.ArgumentParser: def build_parser() -> argparse.ArgumentParser:
parser = target_parser("Generate annotated PDFs with checkboxes.") parser = target_parser("Generate annotated PDFs with checkboxes.")
parser.add_argument("--clear-import", action="store_true",
help="Empty IMPORT_DIR after successful annotation generation")
parser.add_argument( parser.add_argument(
"--overwrite", action="store_true", help="Replace existing outputs" "--overwrite", action="store_true", help="Replace existing outputs"
) )
@@ -364,6 +372,7 @@ def main(argv: Sequence[str] | None = None) -> int:
target, target,
overwrite=args.overwrite, overwrite=args.overwrite,
refaire=args.refaire, refaire=args.refaire,
clear_import=args.clear_import,
) )
return execute(parser, argv, handler) return execute(parser, argv, handler)
+26 -1
View File
@@ -5,15 +5,40 @@ from collections.abc import Sequence
from pathlib import Path from pathlib import Path
from copienator import ( from copienator import (
CliError,
EvaluationWorkspace, EvaluationWorkspace,
ExitCode, ExitCode,
evaluation_parser, evaluation_parser,
execute, execute,
workspace_from_args, workspace_from_args,
) )
from copienator.configuration import IMPORT_DIR from copienator import configuration
ANNOTATION_DIRECTORIES = ("BGnot", "Bnot", "Anot") ANNOTATION_DIRECTORIES = ("BGnot", "Bnot", "Anot")
IMPORT_DIR = getattr(configuration, "IMPORT_DIR", None)
def clear_import_directory(workspace: EvaluationWorkspace) -> None:
"""Empty the configured import folder while preserving the folder itself."""
if IMPORT_DIR is None or not str(IMPORT_DIR).strip():
print("Aucun dossier d’import configuré ; aucun fichier supprimé.")
return
directory = Path(IMPORT_DIR).expanduser().resolve()
if workspace.root.resolve().is_relative_to(directory) or Path.cwd().resolve().is_relative_to(directory):
raise CliError(f"Le dossier d’import ne peut pas contenir l’évaluation ou le projet : {directory}",
ExitCode.INVALID_ARGUMENTS)
if not directory.exists():
print(f"Dossier d’import absent ; aucun fichier supprimé : {directory}")
return
if not directory.is_dir():
raise CliError(f"Le dossier d’import n’est pas un dossier : {directory}",
ExitCode.INVALID_ARGUMENTS)
for child in directory.iterdir():
if child.is_dir() and not child.is_symlink():
shutil.rmtree(child)
else:
child.unlink()
print(f"Dossier d’import vidé : {directory}", flush=True)
def sync_annotated( def sync_annotated(
+6
View File
@@ -527,6 +527,12 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
), ),
arguments=( arguments=(
arg_target("le dossier de l’évaluation"), arg_target("le dossier de l’évaluation"),
ArgumentSpec(
"clear_import", "Vider le dossier d’import", "bool", "--clear-import",
default=bool(str(getattr(configuration, "IMPORT_DIR", "") or "").strip()),
help="Supprime tous les fichiers et sous-dossiers du dossier IMPORT_DIR configuré "
"après une génération réussie des copies annotées. Le dossier lui-même est conservé.",
),
ArgumentSpec( ArgumentSpec(
"overwrite", "overwrite",
"Écraser les sorties", "Écraser les sorties",
+13
View File
@@ -52,6 +52,19 @@ class GuiConvenienceTests(unittest.TestCase):
start.assert_not_called() start.assert_not_called()
self.assertEqual(self.app.state_store.step(step.id)["status"], "success") self.assertEqual(self.app.state_store.step(step.id)["status"], "success")
def test_annotation_import_cleanup_can_be_unchecked(self):
self.app.tree.selection_set("annotation")
self.app.update()
self.assertTrue(self.app.arg_vars["clear_import"].get())
self.assertIn("--clear-import", self.app._make_command())
self.app.arg_vars["clear_import"].set(False)
self.assertNotIn("--clear-import", self.app._make_command())
self.app.tree.selection_set("export")
self.app.update()
self.app.tree.selection_set("annotation")
self.app.update()
self.assertFalse(self.app.arg_vars["clear_import"].get())
def test_cutleft_visual_check_option_is_below_and_off_by_default(self): def test_cutleft_visual_check_option_is_below_and_off_by_default(self):
step = self.app.step_by_id["cutleft"] step = self.app.step_by_id["cutleft"]
self.app.current_step = step self.app.current_step = step
+13
View File
@@ -2705,6 +2705,19 @@ class WorkflowTests(unittest.TestCase):
) )
self.assertEqual(command[4], "annotate-grouped") self.assertEqual(command[4], "annotate-grouped")
def test_annotation_import_cleanup_defaults_to_configured_folder(self) -> None:
for configured in (None, "", Path("Import")):
with patch.object(workflow_module.configuration, "IMPORT_DIR", configured):
step = next(step for step in build_workflow(False) if step.id == "annotation")
for variant in step.variants:
with self.subTest(configured=configured, variant=variant.id):
command = build_command(REPOSITORY, step, variant, {}, self.evaluation)
self.assertEqual("--clear-import" in command, bool(configured))
command = build_command(
REPOSITORY, step, variant, {"clear_import": False}, self.evaluation
)
self.assertNotIn("--clear-import", command)
def test_copy_preparation_commands_are_python(self) -> None: def test_copy_preparation_commands_are_python(self) -> None:
rotate = self.command("rotate", "rotate", {"target": self.evaluation}) rotate = self.command("rotate", "rotate", {"target": self.evaluation})
rename = self.command("rename", "rename", {"target": self.evaluation}) rename = self.command("rename", "rename", {"target": self.evaluation})