Spaces:
Sleeping
Sleeping
Download app.py from Alokik089/redaction_tool: direct link, hf CLI and curl.
- Browser
- Download file 5.3 kB
-
https://huggingface.co/spaces/Alokik089/redaction_tool/resolve/main/app.py
- Command line
-
hf download hf://spaces/Alokik089/redaction_tool/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Alokik089/redaction_tool/resolve/main/app.py
5.3 kB
| """Entry point for the FREE Hugging Face Spaces path (SDK: gradio + | |
| ZeroGPU hardware) -- see this folder's README.md for the why and setup. | |
| This is a genuine Gradio app, not a FastAPI app with Gradio bolted on -- | |
| that was the first design here, and it turned out to be a dead end: | |
| Hugging Face confirmed directly ("ZeroGPU Spaces are exclusively | |
| compatible with the Gradio SDK") that ZeroGPU's @spaces.GPU detection | |
| only works for functions actually invoked through Gradio's own | |
| `launch()`/event system, not a custom `uvicorn.run()` -- even one with | |
| Gradio mounted onto it. Real errors seen on an actual deploy before this | |
| rewrite: "No @spaces.GPU function detected during startup", and a port | |
| 7860 bind conflict (Gradio's own launch process trying to start | |
| alongside our manual uvicorn.run() call). | |
| What's unchanged despite this rewrite: `redactor/` -- the actual | |
| detection and redaction logic -- is untouched and imported directly, | |
| exactly as scripts/redact.py does. `api/` (the FastAPI backend used by | |
| every other deploy path) isn't used here at all; this file is its own | |
| self-contained UI calling the same underlying engine. | |
| One real, unavoidable tradeoff, same as before this rewrite: this sets | |
| REDACTOR_SPACY_MODEL to en_core_web_lg -- a lower-accuracy model than | |
| en_core_web_trf (see EVALUATION.md) -- because en_core_web_trf needs | |
| transformers, which needs huggingface-hub<1.0, which conflicts with the | |
| gradio version Hugging Face's own build pipeline force-installs here | |
| (needs huggingface-hub>=1.16). Confirmed via a real ResolutionImpossible | |
| on an actual build, not inferred. | |
| """ | |
| import os | |
| os.environ.setdefault("REDACTOR_SPACY_MODEL", "en_core_web_lg") | |
| import tempfile | |
| from pathlib import Path | |
| import gradio as gr | |
| import spaces | |
| from redactor.docx_io import redact_docx | |
| from redactor.engine import RedactionEngine | |
| # Built once at import time, not per upload -- loading the spaCy model is | |
| # the expensive part; every upload reuses this same engine, same as | |
| # api/main.py's lifespan does for the other deploy paths. | |
| print("Loading RedactionEngine...") | |
| _engine = RedactionEngine() | |
| print("RedactionEngine ready.") | |
| def _zerogpu_eligibility_placeholder(): | |
| """Exists only so this Space, on ZeroGPU hardware, has at least one | |
| @spaces.GPU function genuinely reachable through Gradio's own event | |
| system (wired to demo.load() below) -- a hard ZeroGPU platform | |
| requirement, confirmed via a real failed deploy, not decorative. | |
| The actual redaction work in redact_uploaded_file() below is | |
| deliberately NOT decorated with @spaces.GPU: it's CPU-only | |
| (en_core_web_lg needs no GPU at all), and decorating it would spend | |
| ZeroGPU's very small daily quota (3.5 minutes/day, 3 requests/day) on | |
| every single upload for zero benefit -- the app would become | |
| unusable beyond a couple of test runs per day for no reason. | |
| """ | |
| return None | |
| def redact_uploaded_file(file_path: str, progress: gr.Progress = gr.Progress()): | |
| """The actual product: takes an uploaded .docx, returns a redacted | |
| one plus a short summary. Runs on plain CPU, not GPU-decorated -- | |
| see _zerogpu_eligibility_placeholder's docstring for why that's | |
| deliberate, not an oversight.""" | |
| if file_path is None: | |
| raise gr.Error("Upload a .docx file first.") | |
| output_path = str(Path(tempfile.mkdtemp()) / "redacted.docx") | |
| def on_progress(units_done: int, units_total: int): | |
| fraction = units_done / units_total if units_total else 0 | |
| progress(fraction, desc=f"Redacting... {units_done}/{units_total} sections") | |
| try: | |
| replacement_log = redact_docx(file_path, output_path, engine=_engine, on_unit_done=on_progress) | |
| except Exception as e: | |
| raise gr.Error(f"Redaction failed: {e}") | |
| summary = f"{len(replacement_log)} value(s) redacted." | |
| return output_path, summary | |
| with gr.Blocks(title="BetterRedactionTool") as demo: | |
| gr.Markdown( | |
| "# BetterRedactionTool\n" | |
| "Upload a `.docx` file. PII is detected and swapped for consistent, " | |
| "realistic fake values -- the same real name/email/etc. always " | |
| "becomes the same fake one everywhere in the document." | |
| ) | |
| file_input = gr.File(label="Upload .docx", file_types=[".docx"], file_count="single", type="filepath") | |
| redact_button = gr.Button("Redact", variant="primary") | |
| summary_output = gr.Textbox(label="Result", interactive=False) | |
| file_output = gr.File(label="Download redacted.docx") | |
| redact_button.click( | |
| fn=redact_uploaded_file, | |
| inputs=file_input, | |
| outputs=[file_output, summary_output], | |
| ) | |
| # Registers _zerogpu_eligibility_placeholder as a real Gradio event | |
| # handler (fires once, when the UI loads) -- this is what makes it | |
| # "genuinely reachable through Gradio's own event system" rather | |
| # than just a decorated function sitting unused in the file. | |
| demo.load(fn=_zerogpu_eligibility_placeholder, inputs=None, outputs=None) | |
| if __name__ == "__main__": | |
| # No explicit host/port: Hugging Face's Gradio-SDK runtime sets | |
| # GRADIO_SERVER_NAME/GRADIO_SERVER_PORT, and demo.launch() reads | |
| # them automatically. Hardcoding our own here is what caused the | |
| # port-7860 bind conflict in the previous (non-Gradio-native) design. | |
| demo.launch() | |