Skip to content

Commit 8d24914

Browse files
committed
feat(logging): report library warnings as log records
- 17 warnings.warn calls across 6 modules become _logger.warning - auth.py keeps its DeprecationWarning, the one correct use left - page_package.py gains the module logger it lacked - tests assert on caplog instead of pytest.warns - get-started.md notes that a warnings filter no longer applies
1 parent 48b9537 commit 8d24914

11 files changed

Lines changed: 82 additions & 86 deletions

File tree

‎docs/get-started.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ osw.set_log_level("DEBUG") # see more
7777
osw.disable_logging() # detach the handler osw attached
7878
```
7979

80+
Problems that osw can work around are WARNING records on the same logger, not
81+
Python `warnings`. A truncated query result and a page that does not exist are
82+
examples. A `warnings` filter or the `-W` option therefore has no effect on
83+
them. A message that repeats is also written out every time, where the warnings
84+
machinery would have shown it once. Use `set_log_level` to control them.
85+
8086
The records go to `stderr`, which leaves `stdout` free for your program's own
8187
output. That matters for anything speaking a protocol over `stdout`, such as an
8288
MCP stdio server. Pass `osw.enable_logging(stream=...)` to send them elsewhere.

‎src/osw/controller/page_package.py‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import importlib.util
22
import json
3+
import logging
34
import re
45
import sys
56
from pathlib import Path
67
from typing import Optional, Union
7-
from warnings import warn
88

99
from opensemantic.v1 import OswBaseModel
1010
from pydantic.v1 import FilePath
@@ -17,6 +17,8 @@
1717
from osw.utils.regex import RegExPatternExtended
1818
from osw.wtsite import WtPage, WtSite
1919

20+
_logger = logging.getLogger(__name__)
21+
2022
# Definition of constants
2123
PATTERNS = {
2224
"Category string with prefix in quotes": RegExPatternExtended(
@@ -267,7 +269,7 @@ def find_first_package_dir(
267269
# expected: not every search path holds every package
268270
continue
269271
except ValueError as e:
270-
warn(
272+
_logger.warning(
271273
f"Multiple elements {package_or_script_name} found in "
272274
f"{search_path}: {e}"
273275
)
@@ -697,7 +699,7 @@ def recursive(
697699
new_listed_pages = []
698700
required_packages = []
699701
if script_path is None:
700-
warn(
702+
_logger.warning(
701703
f"Package script for {package_to_process} not found in any "
702704
f"of the search paths: {search_paths}"
703705
)
@@ -711,7 +713,7 @@ def recursive(
711713
package_script
712714
)
713715
except Exception as e:
714-
warn(
716+
_logger.warning(
715717
f"Error reading package script for "
716718
f"{package_to_process}: {e}"
717719
)
@@ -724,7 +726,7 @@ def recursive(
724726
new_listed_pages = []
725727
required_packages = []
726728
if package_dir is None:
727-
warn(
729+
_logger.warning(
728730
f"Package info for {package_to_process} not found in any "
729731
f"of the search paths: {search_paths}"
730732
)
@@ -738,7 +740,7 @@ def recursive(
738740
get_required_packages_from_package_info_file(package_info)
739741
)
740742
except Exception as e:
741-
warn(
743+
_logger.warning(
742744
f"Error reading package info for {package_to_process}: {e}"
743745
)
744746
# Check for redundant pages
@@ -812,7 +814,7 @@ def recursive(
812814
# Report on redundantly listed pages
813815
for pg in rec_ret["redundant_pages"].keys():
814816
pks = rec_ret["redundant_pages"][pg]
815-
warn(f"Page {pg} is listed in {len(pks)} packages!: {pks}")
817+
_logger.warning(f"Page {pg} is listed in {len(pks)} packages!: {pks}")
816818
# Get all listed pages
817819
all_listed_pages = []
818820
for pck in rec_ret["listed_pages"].keys():

‎src/osw/core.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from enum import Enum
1212
from typing import Any, Dict, List, Optional, Type, Union, overload
1313
from uuid import UUID, uuid4
14-
from warnings import warn
1514

1615
import black
1716
import datamodel_code_generator
@@ -286,7 +285,7 @@ def sort_list_of_entities_by_class(
286285
f"as the class does not define a field 'type'."
287286
)
288287
if exclude_typeless:
289-
warn(
288+
_logger.warning(
290289
f"Skipping instance '{entity}' of class '{name}' as the class "
291290
f"does not define a field 'type'."
292291
)

‎src/osw/data/import_utility.py‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import logging
44
import re
55
import uuid as uuid_module
6-
import warnings
76
from pathlib import Path
87
from typing import Any, Callable, Dict, List, Optional, Type, Union
98

@@ -412,9 +411,8 @@ def jsonpath_search_and_return_list(
412411
# Search in a dramatically reduced number of entries
413412
result = jp_parse.find(search_tar[str(cls_type)])
414413
except Exception as e:
415-
warnings.warn(
416-
f"jsonpath_search_and_return_list() threw and exception:\n{e!s}",
417-
stacklevel=2,
414+
_logger.warning(
415+
f"jsonpath_search_and_return_list() threw and exception:\n{e!s}"
418416
)
419417
result = jp_parse.find(search_tar)
420418
else:
@@ -434,10 +432,9 @@ def jsonpath_search_and_return_list(
434432
list_.append(res.value[val_key])
435433

436434
if len(list_) == 0 and warn:
437-
warnings.warn(
435+
_logger.warning(
438436
f"jsonpath_search_and_return_list() did not find any "
439-
f"results for the jsonpath string '{jp_str}'",
440-
stacklevel=3,
437+
f"results for the jsonpath string '{jp_str}'"
441438
)
442439
return list(set(flatten_list(list_)))
443440

‎src/osw/express.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from pathlib import Path
2020
from typing import overload
2121
from uuid import uuid4
22-
from warnings import warn
2322

2423
import requests
2524
from opensemantic.v1 import OswBaseModel
@@ -451,7 +450,7 @@ def build_target_fn(
451450
if mode == FilenameMode.osw_id:
452451
return osw_id_fn
453452
if not name:
454-
warn(
453+
_logger.warning(
455454
f"No name is stored for '{osw_id_fn}', falling back to the OSW-ID as "
456455
f"the file name."
457456
)
@@ -1001,7 +1000,7 @@ def import_with_fallback(
10011000
except Exception as e:
10021001
if dependencies is None:
10031002
dependencies = {}
1004-
warn(
1003+
_logger.warning(
10051004
"No 'dependencies' were passed to the function "
10061005
"import_with_fallback()! Trying to derive them from 'to_import'."
10071006
)
@@ -1018,7 +1017,7 @@ def import_with_fallback(
10181017
"No 'dependencies' were passed to the function import_with_fallback() "
10191018
"and could not be derived from 'to_import'!"
10201019
)
1021-
warn(
1020+
_logger.warning(
10221021
f"An exception occurred while loading the module dependencies: \n"
10231022
f'"{e}"\n'
10241023
"A connection to an OSW instance, to fetch the dependencies from, "

‎src/osw/wiki_tools.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import getpass
22
import logging
33
import re
4-
import warnings
54
from typing import Dict, List, Optional, Tuple, Union
65

76
import mwclient
@@ -327,7 +326,7 @@ def semantic_search_(single_query):
327326
# count format does, means the result count says nothing about
328327
# truncation
329328
if limit and n >= limit:
330-
warnings.warn(
329+
_logger.warning(
331330
f"Query '{single_query}' returned {n} results, which meets the "
332331
f"requested limit of {limit}. Results are truncated - raise "
333332
f"the limit or page through with '|offset=' to retrieve the "
@@ -348,7 +347,7 @@ def semantic_search_(single_query):
348347
else:
349348
dropped += 1
350349
if dropped > 0:
351-
warnings.warn(
350+
_logger.warning(
352351
f"Query '{single_query}': {dropped} of {n} results were dropped "
353352
f"because the wiki reported them as non-existing pages."
354353
)

‎src/osw/wtsite.py‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import shutil
99
import threading
1010
import urllib
11-
import warnings
1211
import xml.etree.ElementTree as et
1312
from copy import deepcopy
1413
from datetime import datetime
@@ -17,7 +16,6 @@
1716
from pprint import pformat
1817
from time import sleep
1918
from typing import Any, Dict, List, Optional, Union
20-
from warnings import warn
2119

2220
import mwclient
2321
import pyld
@@ -420,10 +418,8 @@ def get_page_(title: str, index: int = None):
420418
pages.append(wtpage)
421419
if not wtpage.exists:
422420
if param.raise_warning:
423-
warnings.warn(
424-
f"WARNING: Page with title '{title}' does not exist.",
425-
RuntimeWarning,
426-
3,
421+
_logger.warning(
422+
f"Page with title '{title}' does not exist."
427423
)
428424
# throw argument value exception if page does not exist
429425
raise ValueError(f"Page with title '{title}' does not exist.")
@@ -833,7 +829,7 @@ def delete_single_page(page_: Union["WtPage", MwPage], comment: str):
833829
try:
834830
page_ = self._site.pages[page_]
835831
except Exception as e:
836-
warn(
832+
_logger.warning(
837833
f"Page '{page_}' could not be added to the list of "
838834
f"to-be-deleted pages. The following Exception occurred:\n{e}"
839835
)
@@ -842,7 +838,7 @@ def delete_single_page(page_: Union["WtPage", MwPage], comment: str):
842838
return page_.delete(comment=comment)
843839
return page_.delete(reason=comment)
844840
except Exception as e:
845-
warn(
841+
_logger.warning(
846842
f"Page '{page_}' could not be deleted. "
847843
f"The following Exception occurred:\n{e}"
848844
)

‎tests/integration/performance_test.py‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import time
23

34
import pytest
@@ -8,7 +9,7 @@
89
from osw.wtsite import WtSite
910

1011

11-
def test_fetch_and_load(wiki_domain, wiki_username, wiki_password, mocker):
12+
def test_fetch_and_load(wiki_domain, wiki_username, wiki_password, mocker, caplog):
1213
# create a credential file on the default path for osw express
1314
cm = CredentialManager() # cred_filepath = Path.cwd() / "accounts.pwd.yaml")
1415
cm.add_credential(
@@ -74,17 +75,16 @@ def test_fetch_and_load(wiki_domain, wiki_username, wiki_password, mocker):
7475

7576
assert len(result.pages) == 50
7677

77-
# we should get a warning for the non-existent page
78-
# see https://docs.pytest.org/en/stable/how-to/capture-warnings.html
78+
# we should get a log warning for the non-existent page
7979
pages[0] = "IDONOTEXIST"
80-
with pytest.warns(RuntimeWarning) as record:
81-
result = osw_express.site.get_page(
82-
WtSite.GetPageParam(titles=pages[:50], raise_exception=False)
83-
)
84-
# assert that at least one warning message contains
85-
# the missing page title to inform the user
86-
assert any("IDONOTEXIST" in str(rec.message) for rec in record)
87-
assert len(result.pages) == 50 # assert that the result is still returned
80+
caplog.set_level(logging.WARNING, logger="osw")
81+
result = osw_express.site.get_page(
82+
WtSite.GetPageParam(titles=pages[:50], raise_exception=False)
83+
)
84+
# assert that at least one warning message contains
85+
# the missing page title to inform the user
86+
assert any("IDONOTEXIST" in record.message for record in caplog.records)
87+
assert len(result.pages) == 50 # assert that the result is still returned
8888

8989
# assert that ValueError is raised when the page does not exist
9090
pages[0] = "IDONOTEXIST"

‎tests/integration/test_express_init.py‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,6 @@ def test_none_to_import_raises(self):
781781
with pytest.raises(ValueError, match="to_import"):
782782
import_with_fallback(None, {})
783783

784-
@pytest.mark.filterwarnings("ignore:No 'dependencies' were passed:UserWarning")
785784
def test_nonexistent_class_no_deps_raises(self):
786785
"""When the class doesn't exist and no dependencies or osw_fpt
787786
are given, should raise AttributeError."""
@@ -1228,10 +1227,6 @@ def test_live_upload_with_target_fpt(
12281227
class TestLiveImportWithFallback:
12291228
"""Test import_with_fallback fallback path with live wiki."""
12301229

1231-
@pytest.mark.filterwarnings("ignore:No 'dependencies' were passed:UserWarning")
1232-
@pytest.mark.filterwarnings(
1233-
"ignore:An exception occurred while loading the module dependencies:UserWarning"
1234-
)
12351230
def test_live_fallback_fetches_from_wiki(
12361231
self, wiki_domain, wiki_username, wiki_password
12371232
):

‎tests/test_download_filename_modes.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
file name, or combine both.
55
"""
66

7+
import logging
8+
79
import pytest
810

911
from osw.express import FilenameMode, build_target_fn
@@ -67,9 +69,11 @@ def test_a_directory_in_the_name_is_dropped():
6769

6870
@pytest.mark.parametrize("name", [None, ""])
6971
@pytest.mark.parametrize("mode", [FilenameMode.name, FilenameMode.name_and_osw_id])
70-
def test_a_missing_name_falls_back_to_the_osw_id(mode, name):
71-
with pytest.warns(UserWarning, match="No name is stored"):
72-
assert build_target_fn(mode, OSW_ID_FN, name) == OSW_ID_FN
72+
def test_a_missing_name_falls_back_to_the_osw_id(mode, name, caplog):
73+
caplog.set_level(logging.WARNING, logger="osw")
74+
assert build_target_fn(mode, OSW_ID_FN, name) == OSW_ID_FN
75+
76+
assert any("No name is stored" in record.message for record in caplog.records)
7377

7478

7579
def test_an_unknown_mode_is_rejected():

0 commit comments

Comments
 (0)