Skip to content

Commit 2aaedcf

Browse files
committed
Office: use literal imports (clear Semgrep non-literal-import); guard missing-file test
1 parent d396263 commit 2aaedcf

2 files changed

Lines changed: 33 additions & 12 deletions

File tree

‎je_auto_control/utils/office/office.py‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,39 @@
1111
:class:`RuntimeError` when the backing library is missing, so the core
1212
package stays lean and import-time stays Qt-free / dependency-free.
1313
"""
14-
import importlib
1514
from pathlib import Path
1615
from typing import Any, Dict, List
1716

17+
_HINT = "pip install je_auto_control[office]"
1818

19-
def _import(module_name: str, pip_name: str) -> Any:
20-
"""Import an optional Office backend or raise a helpful error."""
19+
20+
def _openpyxl() -> Any:
21+
"""Import openpyxl (optional Excel backend) or raise a helpful error."""
22+
try:
23+
import openpyxl
24+
except ImportError as error:
25+
raise RuntimeError(f"Excel I/O requires openpyxl ({_HINT}).") from error
26+
return openpyxl
27+
28+
29+
def _docx() -> Any:
30+
"""Import python-docx (optional Word backend) or raise a helpful error."""
31+
try:
32+
import docx
33+
except ImportError as error:
34+
raise RuntimeError(
35+
f"Word I/O requires python-docx ({_HINT}).") from error
36+
return docx
37+
38+
39+
def _pptx() -> Any:
40+
"""Import python-pptx (optional PPT backend) or raise a helpful error."""
2141
try:
22-
return importlib.import_module(module_name)
42+
import pptx
2343
except ImportError as error:
2444
raise RuntimeError(
25-
f"This feature requires {pip_name} "
26-
f"(pip install je_auto_control[office]).") from error
45+
f"PowerPoint I/O requires python-pptx ({_HINT}).") from error
46+
return pptx
2747

2848

2949
def _existing(path: str) -> Path:
@@ -40,7 +60,7 @@ def read_workbook(path: str, sheet: str = "") -> List[Dict[str, Any]]:
4060
4161
``sheet`` defaults to the active sheet.
4262
"""
43-
openpyxl = _import("openpyxl", "openpyxl")
63+
openpyxl = _openpyxl()
4464
workbook = openpyxl.load_workbook(filename=str(_existing(path)),
4565
read_only=True, data_only=True)
4666
try:
@@ -58,7 +78,7 @@ def read_workbook(path: str, sheet: str = "") -> List[Dict[str, Any]]:
5878
def write_workbook(path: str, rows: List[Dict[str, Any]],
5979
sheet: str = "Sheet1") -> str:
6080
"""Write ``rows`` (list of dicts) to an ``.xlsx`` file; return the path."""
61-
openpyxl = _import("openpyxl", "openpyxl")
81+
openpyxl = _openpyxl()
6282
workbook = openpyxl.Workbook()
6383
worksheet = workbook.active
6484
worksheet.title = sheet
@@ -77,14 +97,14 @@ def write_workbook(path: str, rows: List[Dict[str, Any]],
7797

7898
def read_document(path: str) -> Dict[str, List[str]]:
7999
"""Read a ``.docx`` file's paragraph texts."""
80-
docx = _import("docx", "python-docx")
100+
docx = _docx()
81101
document = docx.Document(str(_existing(path)))
82102
return {"paragraphs": [para.text for para in document.paragraphs]}
83103

84104

85105
def write_document(path: str, paragraphs: List[str]) -> str:
86106
"""Write ``paragraphs`` to a ``.docx`` file; return the path."""
87-
docx = _import("docx", "python-docx")
107+
docx = _docx()
88108
document = docx.Document()
89109
for paragraph in paragraphs:
90110
document.add_paragraph(str(paragraph))
@@ -97,7 +117,7 @@ def write_document(path: str, paragraphs: List[str]) -> str:
97117

98118
def read_presentation(path: str) -> Dict[str, List[List[str]]]:
99119
"""Read a ``.pptx`` file's per-slide text runs."""
100-
pptx = _import("pptx", "python-pptx")
120+
pptx = _pptx()
101121
presentation = pptx.Presentation(str(_existing(path)))
102122
slides = []
103123
for slide in presentation.slides:
@@ -121,7 +141,7 @@ def _add_slide(presentation: Any, layout: Any, spec: Any) -> None:
121141

122142
def write_presentation(path: str, slides: List[Any]) -> str:
123143
"""Write ``slides`` (each ``{title, body:[...]}``) to a ``.pptx`` file."""
124-
pptx = _import("pptx", "python-pptx")
144+
pptx = _pptx()
125145
presentation = pptx.Presentation()
126146
layout = presentation.slide_layouts[1] # "Title and Content"
127147
for spec in slides:

‎test/unit_test/headless/test_office_batch.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def test_powerpoint_roundtrip(tmp_path):
4040

4141

4242
def test_read_missing_file_raises():
43+
pytest.importorskip("openpyxl")
4344
with pytest.raises(FileNotFoundError):
4445
read_workbook("does-not-exist-12345.xlsx")
4546

0 commit comments

Comments
 (0)