Skip to content

Commit bdfd2e9

Browse files
committed
fix(packaging): suppress ERROR logging for optional feature checks
When checking for tidy3d-extras availability using check_tidy3d_extras_licensed_feature, ERROR messages were logged to console even when the caller intended to catch and handle the exception gracefully. This caused confusing UX in the supports_local_subpixel decorator when use_local_subpixel=None (the default). Add a quiet parameter to suppress logging when the check is speculative. The Tidy3dError base class now accepts log_error=True parameter, allowing exceptions to be constructed without automatic error logging. Fixes FXC-4374
1 parent 8581468 commit bdfd2e9

File tree

3 files changed

+68
-13
lines changed

3 files changed

+68
-13
lines changed

tests/test_components/test_packaging.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,45 @@ def get_flag():
154154
reload_config(profile="default")
155155

156156

157+
def test_supports_local_subpixel_no_error_logged_when_optional(monkeypatch, caplog):
158+
"""Regression test for FXC-4374: no ERROR should be logged when preference=None
159+
and tidy3d-extras is unavailable, since the decorator handles it gracefully."""
160+
import logging
161+
162+
reload_config(profile="default")
163+
tidy3d_extras["mod"] = None
164+
tidy3d_extras["use_local_subpixel"] = None
165+
166+
real_import = builtins.__import__
167+
168+
def fake_import(name, globals=None, locals=None, fromlist=(), level=0):
169+
if name == "tidy3d_extras":
170+
raise ImportError("forced failure")
171+
return real_import(name, globals, locals, fromlist, level)
172+
173+
monkeypatch.setattr(builtins, "__import__", fake_import)
174+
175+
try:
176+
# preference=None is the default - feature is optional
177+
config.update_section("simulation", use_local_subpixel=None)
178+
179+
@supports_local_subpixel
180+
def get_flag():
181+
return tidy3d_extras["use_local_subpixel"]
182+
183+
with caplog.at_level(logging.ERROR):
184+
result = get_flag()
185+
186+
# Should fall back gracefully without logging errors
187+
assert result is False
188+
assert not any("tidy3d-extras" in record.message for record in caplog.records), (
189+
"ERROR was logged but should have been suppressed for optional feature check"
190+
)
191+
finally:
192+
tidy3d_extras["mod"] = None
193+
tidy3d_extras["use_local_subpixel"] = None
194+
reload_config(profile="default")
195+
196+
157197
if __name__ == "__main__":
158198
pytest.main()

tidy3d/exceptions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
class Tidy3dError(ValueError):
1111
"""Any error in tidy3d"""
1212

13-
def __init__(self, message: Optional[str] = None) -> None:
13+
def __init__(self, message: Optional[str] = None, log_error: bool = True) -> None:
1414
"""Log just the error message and then raise the Exception."""
1515
super().__init__(message)
16-
log.error(message)
16+
if log_error:
17+
log.error(message)
1718

1819

1920
class ConfigError(Tidy3dError):

tidy3d/packaging.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,14 @@ def get_numpy_major_version(module=np):
183183
return major_version
184184

185185

186-
def _check_tidy3d_extras_available():
186+
def _check_tidy3d_extras_available(quiet: bool = False):
187187
"""Helper function to check if 'tidy3d-extras' is available and version matched.
188188
189+
Parameters
190+
----------
191+
quiet : bool
192+
If True, suppress error logging when raising exceptions.
193+
189194
Raises
190195
------
191196
Tidy3dImportError
@@ -199,49 +204,56 @@ def _check_tidy3d_extras_available():
199204
raise Tidy3dImportError(
200205
"The package 'tidy3d-extras' is absent. "
201206
"Please install the 'tidy3d-extras' package using, for "
202-
r"example, 'pip install tidy3d\[extras]'."
207+
r"example, 'pip install tidy3d\[extras]'.",
208+
log_error=not quiet,
203209
)
204210

205211
try:
206212
import tidy3d_extras as tidy3d_extras_mod
207213

208214
except ImportError as exc:
209215
raise Tidy3dImportError(
210-
"The package 'tidy3d-extras' did not initialize correctly."
216+
"The package 'tidy3d-extras' did not initialize correctly.",
217+
log_error=not quiet,
211218
) from exc
212219

213220
if not hasattr(tidy3d_extras_mod, "__version__"):
214221
raise Tidy3dImportError(
215222
"The package 'tidy3d-extras' did not initialize correctly. "
216223
"Please install the 'tidy3d-extras' package using, for "
217-
r"example, 'pip install tidy3d\[extras]'."
224+
r"example, 'pip install tidy3d\[extras]'.",
225+
log_error=not quiet,
218226
)
219227

220228
version = tidy3d_extras_mod.__version__
221229

222230
if version is None:
223231
raise Tidy3dImportError(
224232
"The package 'tidy3d-extras' did not initialize correctly, "
225-
"likely due to an invalid API key."
233+
"likely due to an invalid API key.",
234+
log_error=not quiet,
226235
)
227236

228237
if version != __version__:
229238
raise Tidy3dImportError(
230239
f"The version of 'tidy3d-extras' is {version}, but the version of 'tidy3d' is {__version__}. "
231240
"They must match. You can install the correct "
232-
r"version using 'pip install tidy3d\[extras]'."
241+
r"version using 'pip install tidy3d\[extras]'.",
242+
log_error=not quiet,
233243
)
234244

235245
tidy3d_extras["mod"] = tidy3d_extras_mod
236246

237247

238-
def check_tidy3d_extras_licensed_feature(feature_name: str):
248+
def check_tidy3d_extras_licensed_feature(feature_name: str, quiet: bool = False):
239249
"""Helper function to check if a specific feature is licensed in 'tidy3d-extras'.
240250
241251
Parameters
242252
----------
243253
feature_name : str
244254
The name of the feature to check for.
255+
quiet : bool
256+
If True, suppress error logging when raising exceptions.
245257
246258
Raises
247259
------
@@ -250,17 +262,19 @@ def check_tidy3d_extras_licensed_feature(feature_name: str):
250262
"""
251263

252264
try:
253-
_check_tidy3d_extras_available()
265+
_check_tidy3d_extras_available(quiet=quiet)
254266
except Tidy3dImportError as exc:
255267
raise Tidy3dImportError(
256-
f"The package 'tidy3d-extras' is required for this feature '{feature_name}'."
268+
f"The package 'tidy3d-extras' is required for this feature '{feature_name}'.",
269+
log_error=not quiet,
257270
) from exc
258271

259272
features = tidy3d_extras["mod"].extension._features()
260273
if feature_name not in features:
261274
raise Tidy3dImportError(
262275
f"The feature '{feature_name}' is not available with your license. "
263-
"Please contact Tidy3D support, or upgrade your license."
276+
"Please contact Tidy3D support, or upgrade your license.",
277+
log_error=not quiet,
264278
)
265279

266280

@@ -277,7 +291,7 @@ def _fn(*args: Any, **kwargs: Any):
277291
return fn(*args, **kwargs)
278292

279293
try:
280-
check_tidy3d_extras_licensed_feature("local_subpixel")
294+
check_tidy3d_extras_licensed_feature("local_subpixel", quiet=True)
281295
except Tidy3dImportError as exc:
282296
tidy3d_extras["use_local_subpixel"] = False
283297
if preference is True:

0 commit comments

Comments
 (0)