diff --git a/bindings/python/dlite-misc-python.i b/bindings/python/dlite-misc-python.i index c93db9c54..efd6e1021 100644 --- a/bindings/python/dlite-misc-python.i +++ b/bindings/python/dlite-misc-python.i @@ -84,6 +84,29 @@ class errctl(): silent = errctl(hide=True) """Context manager for a silent code block. Same as `errctl(hide=True)`.""" +class HideDLiteWarnings(): + """Context manager for hiding warnings. + + Arguments: + hide: If true, hide warnings if `pattern` is None or `pattern` + matches the warning message. + If false, hide warnings if `pattern` is given and don't + match the warning message. + pattern: Optional glob pattern matching the warning message. + + """ + def __init__(self, hide=True, pattern=None): + self.hide = int(hide) + self.pattern = pattern + + def __enter__(self): + self._oldstate = get_warnings_hide() + set_warnings_hide(self.hide, self.pattern) + + def __exit__(self, *exc): + set_warnings_hide(*self._oldstate) + + # A set for keeping track of already issued deprecation warnings _deprecation_warning_record = set() diff --git a/bindings/python/dlite-misc.i b/bindings/python/dlite-misc.i index d4d6758f3..6a2ce71ec 100644 --- a/bindings/python/dlite-misc.i +++ b/bindings/python/dlite-misc.i @@ -72,7 +72,6 @@ return 0; } - %} %include @@ -242,6 +241,17 @@ void _dlite_err_mask_set(int64_t mask); void errcheck(void); +%feature("docstring", "\ +Return a list with the `[hide, pattern]` parameters for controlling whether +warnings are shown. +") dlite_get_warnings_hide; +int dlite_get_warnings_hide(const char **ARGOUT); + +%feature("docstring", "Sets parameters controlling whether to hide warnings.") + dlite_set_warnings_hide; +void dlite_set_warnings_hide(int hide, char *pattern=NULL); + + /* ------------------------------ * Expose other utility functions * ------------------------------ */ diff --git a/bindings/python/dlite-python.i b/bindings/python/dlite-python.i index 531eb97b3..78433ad27 100644 --- a/bindings/python/dlite-python.i +++ b/bindings/python/dlite-python.i @@ -1340,6 +1340,24 @@ PyObject *dlite_run_file(const char *path, PyObject *globals, PyObject *locals) * Argout typemaps * --------------- */ +/* Argout string */ +/* Assumes that *ARGOUT is const buffer owned by the wrapped function */ +%typemap("doc") (const char **ARGOUT) "string" +%typemap(in,numinputs=0) (const char **ARGOUT) (char *tmp=NULL) { + $1 = &tmp; +} +%typemap(argout) (const char **ARGOUT) { + PyObject *argout; + if (tmp$argnum) { + argout = PyUnicode_FromString(tmp$argnum); + } else { + argout = Py_None; + Py_INCREF(argout); + } + $result = SWIG_Python_AppendOutput($result, argout, 0); +} + + /* Argout string */ /* Assumes that *ARGOUT_STRING is malloc()'ed by the wrapped function */ %typemap("doc") (char **ARGOUT_STRING, size_t *LENGTH) "string" diff --git a/bindings/python/tests/test_collection.py b/bindings/python/tests/test_collection.py index c190ba72d..967c6d53e 100644 --- a/bindings/python/tests/test_collection.py +++ b/bindings/python/tests/test_collection.py @@ -87,12 +87,8 @@ assert inst1b != inst2 # Cannot add an instance with an existing label -try: +with raises(dlite.DLiteValueError): coll.add("inst1", inst2) -except dlite.DLiteError: - pass -else: - raise RuntimeError("should not be able to replace an existing instance") coll.add("inst1", inst2, force=True) # forced replacement assert coll.get("inst1") == inst2 diff --git a/bindings/python/tests/test_collection_load.py b/bindings/python/tests/test_collection_load.py index 9cb9b527c..e04e4c6d0 100644 --- a/bindings/python/tests/test_collection_load.py +++ b/bindings/python/tests/test_collection_load.py @@ -21,5 +21,6 @@ # Check that we can load collection from dict d = json.load(open(indir / "coll.json", "r")) -coll = instance_from_dict(d) +with dlite.HideDLiteWarnings(): + coll = instance_from_dict(d) assert coll.nrelations == 6 diff --git a/bindings/python/tests/test_entity.py b/bindings/python/tests/test_entity.py index fe3ad2a6d..5a7afe254 100644 --- a/bindings/python/tests/test_entity.py +++ b/bindings/python/tests/test_entity.py @@ -17,10 +17,14 @@ dlite.storage_path.append(indir / "*.json") dlite.storage_path.append(entitydir / "*.json") +# Hide warnings +dlite.set_warnings_hide( + True, "Warning: Could not load the following Python plugins:*" +) # Load metadata (i.e. an instance of meta-metadata) from url myentity = Instance.from_url(f"json://{entitydir}/MyEntity.json") -print(myentity.uuid) +#print(myentity.uuid) # Check some properties of the entity assert myentity.uuid == "a0e63529-3397-5c4f-a56c-14bf07ecc219" @@ -74,12 +78,13 @@ # Print the value of all properties -for i in range(len(inst)): - print("prop%d:" % i, inst[i]) +# for i in range(len(inst)): +# print("prop%d:" % i, inst[i]) # String representation (as json) # print(inst) + # Check save and load inst.save(f"json://{outdir}/test_entity_inst.json?mode=w") inst2 = Instance.from_url(f"json://{outdir}/test_entity_inst.json") @@ -115,7 +120,8 @@ del inst2 # Make sure we fail with an exception for pathetic cases -with raises(dlite.DLiteStorageOpenError): +# FIXME: a part of the error message is not hidden... +with raises(dlite.DLiteError): Instance.from_location("json", "/", "mode=r") with raises(dlite.DLiteStorageLoadError): @@ -156,7 +162,8 @@ # Check pickling s = pickle.dumps(inst) -inst3 = pickle.loads(s) +with dlite.HideDLiteWarnings(): + inst3 = pickle.loads(s) dim = Dimension("N") @@ -235,7 +242,8 @@ except ImportError: pass else: - inst.save(f"yaml://{outdir}/test_entity.yaml?mode=w") + with dlite.HideDLiteWarnings(): + inst.save(f"yaml://{outdir}/test_entity.yaml?mode=w") # Test metadata diff --git a/bindings/python/tests/test_jstore.py b/bindings/python/tests/test_jstore.py index 2f543f4bf..9bbbc7060 100644 --- a/bindings/python/tests/test_jstore.py +++ b/bindings/python/tests/test_jstore.py @@ -72,7 +72,8 @@ # Test format_dict(), dimension as list of numbers - need metadata js = dlite.JStore() js.load_dict(D1) -meta = js.get() +with dlite.HideDLiteWarnings(): + meta = js.get() assert dlite.format_dict(d3, soft7=True, single=True) == d1 assert dlite.format_dict(d3, soft7=False, single=True) == d2 @@ -200,9 +201,8 @@ assert len(dct) == 2 - - key1 = next(js.get_ids()) # d1 = js.get_dict(key1) s1 = js.get_json(key1) -inst1 = js.get(key1) +with dlite.HideDLiteWarnings(): + inst1 = js.get(key1) diff --git a/bindings/python/tests/test_misc.py b/bindings/python/tests/test_misc.py index 2760bd14e..f988d98ca 100755 --- a/bindings/python/tests/test_misc.py +++ b/bindings/python/tests/test_misc.py @@ -49,7 +49,8 @@ print("No DLite error message is printed to screen:") try: with dlite.errctl(hide=(dlite.DLiteStorageOpenError, dlite.DLiteError)): - dlite.Instance.from_location("-", "__non-existing__") + with dlite.HideDLiteWarnings(): + dlite.Instance.from_location("-", "__non-existing__") except dlite.DLiteStorageOpenError: pass diff --git a/src/dlite-misc.c b/src/dlite-misc.c index 0c2554a4b..9127e0162 100644 --- a/src/dlite-misc.c +++ b/src/dlite-misc.c @@ -16,6 +16,7 @@ #include "utils/session.h" #include "utils/rng.h" #include "utils/uri_encode.h" +#include "utils/globmatch.h" #include "getuuid.h" #include "dlite.h" #include "dlite-macros.h" @@ -487,8 +488,13 @@ int dlite_add_dll_path(void) /* Local state for this module. */ typedef struct { - int in_atexit; - int finalizing; + int in_atexit; // whether we are in atexit() + int finalizing; // whether dlite is finalising + int warnings_hide; // whether to hide warnings + char *warnings_pattern; // If given and `warnings_hide` is true, hide + // warnings matching the glob pattern. Otherwise, + // if `warnings_hide` is false, show only + // warnings matching the glob pattern. } Locals; @@ -501,6 +507,7 @@ Locals *_locals=NULL; /* Free variables in local state. */ static void free_locals() { + if (_locals && _locals->warnings_pattern) free(_locals->warnings_pattern); _locals = NULL; } @@ -561,6 +568,47 @@ void dlite_globals_set(DLiteGlobals *globals_handler) err_set_state(g); } +/* + Return parameters controlling whether warnings should be hidden. + + See dlite_set_warnings_hide() for a description of these parameters. + + If `*pattern` is not NULL, it is assigned to a static pointer to + `warnings_pattern` (owned by DLite). + + Returns `warnings_hide`. + */ +int dlite_get_warnings_hide(const char **pattern) +{ + Locals *locals = get_locals(); + if (pattern) *pattern = locals->warnings_pattern; + return locals->warnings_hide; +} + +/* + Set parameters controlling whether warnings should be hidden. + + Warning parameters: + - `warnings_hide`: whether to hide warnings (see below). + - `warnings_pattern`: glob pattern matching the warning message. + + If `warnings_pattern` is NULL, warnings are hidden if `warnings_hide` + is non-zero. + + If `warnings_pattern` is given, then warnings are hidden if: + - `warnings_pattern` match the warning message and `warnings_hide` is + non-zero. + - `warnings_pattern` don't match the warning message and `warnings_hide` + is zero. + */ +void dlite_set_warnings_hide(int hide, char *pattern) +{ + Locals *locals = get_locals(); + locals->warnings_hide = hide; + if (locals->warnings_pattern) free(locals->warnings_pattern); + locals->warnings_pattern = (pattern) ? strdup(pattern) : NULL; +} + /* Error handler for DLite. @@ -569,15 +617,39 @@ void dlite_globals_set(DLiteGlobals *globals_handler) */ static void dlite_err_handler(const ErrRecord *record) { + Locals *locals = get_locals(); + + if (getenv("DLITE_DEBUG") #ifdef WITH_PYTHON - if (record->level != errLevelError || - getenv("DLITE_PYDEBUG") || - !dlite_err_ignored_get(record->eval)) + || getenv("DLITE_PYDEBUG") +#endif + ) { err_default_handler(record); -#else /* WITH_PYTHON */ - if (!dlite_err_ignored_get(record->eval)) + return; + } + + switch (record->level) { + case errLevelSuccess: + break; + case errLevelWarn: + if (locals->warnings_pattern) { + int match = !globmatch(locals->warnings_pattern, record->msg); + if ((match && !locals->warnings_hide) || + (!match && locals->warnings_hide)) + err_default_handler(record); + } else if (!locals->warnings_hide) { + err_default_handler(record); + } + break; + case errLevelError: + if (!dlite_err_ignored_get(record->eval)) + err_default_handler(record); + break; + case errLevelException: + case errLevelFatal: err_default_handler(record); -#endif + break; + } } /* dlite_errname() with correct call signature */ diff --git a/src/dlite-misc.h b/src/dlite-misc.h index 945614c1d..2bd8c07ff 100644 --- a/src/dlite-misc.h +++ b/src/dlite-misc.h @@ -308,6 +308,43 @@ void dlite_globals_set_atexit(void); /** @} */ +/** + @name Functions controlling whether to hide warnings + + Warning parameters: + - `warnings_hide`: whether to hide warnings (see below). + - `warnings_pattern`: glob pattern matching the warning message. + + If `warnings_pattern` is NULL, warnings are hidden if `warnings_hide` + is non-zero. + + If `warnings_pattern` is not NULL, then warnings are hidden if: + - `warnings_pattern` match the warning message and `warnings_hide` is + non-zero. + - `warnings_pattern` don't match the warning message and `warnings_hide` + is zero. + @{ +*/ + +/** + Return parameters controlling whether warnings should be hidden. + + If `*pattern` is not NULL, it is assigned to a static pointer to + `warnings_pattern` (owned by DLite). + + Returns `warnings_hide`. + */ + +int dlite_get_warnings_hide(const char **pattern); + +/** + Set parameters controlling whether warnings should be hidden. + */ +void dlite_set_warnings_hide(int hide, char *pattern); + +/** @} */ + + /** @name Wrappers around error functions The `DLITE_NOERR()` and `DLITE_NOERR_END` macros are intended to diff --git a/src/utils/tests/test_globmatch.c b/src/utils/tests/test_globmatch.c index d1b9f75b5..4cc8f5f4f 100644 --- a/src/utils/tests/test_globmatch.c +++ b/src/utils/tests/test_globmatch.c @@ -12,13 +12,21 @@ MU_TEST(test_globmatch) { mu_check(0 == globmatch("", "")); mu_check(1 == globmatch("?", "")); + mu_check(0 == globmatch("?", "a")); mu_check(0 == globmatch("*", "")); mu_check(0 == globmatch("?etc*", "/etc/fstab")); + mu_check(0 == globmatch("*etc*", "/etc/fstab")); + mu_check(1 == globmatch("etc*", "/etc/fstab")); + mu_check(0 == globmatch("*tc*", "/etc/fstab")); + mu_check(1 == globmatch("*x*", "/etc/fstab")); mu_check(0 == globmatch("/[a-f]tc*", "/etc/fstab")); mu_check(0 == globmatch("[^A-Za-z]etc*", "/etc/fstab")); mu_check(1 == globmatch("[A-Za-z]etc*", "/etc/fstab")); mu_check(0 == globmatch("\\/etc*", "/etc/fstab")); mu_check(0 == globmatch("?etc*tab", "/etc/fstab")); + mu_check(0 == globmatch("*", "a\nstring\nover multiple\nlines...\n")); + mu_check(1 == globmatch("*.", "a\nstring\nover multiple\nlines...\n")); + mu_check(0 == globmatch("*", "Warning: Behavior change `storageQuery` is not configured. It will be enabled by default from v0.6.0. See https://sintef.github.io/dlite/user_guide/configure_behavior_changes.html for more info.")); } /***********************************************************************/