You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
replace all print statements with proper logging #130
An AST pass over src/osw finds 123 print() calls in real code, spread over 15 modules, led by core.py (29), wtsite.py (23), utils/wikitext.py (17), ontology.py (12) and wiki_tools.py (12). A further 22 sit in the if __name__ == "__main__": demo block of utils/util.py and 47 are already commented out. Both of those groups stay as they are.
Only two modules use logging today, and neither consistently: core.py:71 has a proper _logger = logging.getLogger(__name__) used for five error calls while the same file still has 29 prints, and utils/workflow.py imports logging inside function bodies at lines 195 and 654.
Decisions
Logging is on by default.src/osw/__init__.py attaches a StreamHandler to the osw logger at INFO, writing to sys.stdout, which is where the prints it replaces went. A library that silently swallows its own output would break every existing notebook and script at once, so the default keeps today's behaviour visible.
A one-time notice on import says so and says how to change it:
[INFO] osw: osw logs at INFO. Use osw.set_log_level('WARNING') to see less,
osw.disable_logging() to switch it off, or set OSW_LOG_LEVEL=OFF before import.
New public API: osw.set_log_level(level), osw.disable_logging(), osw.enable_logging(level=None, stream=None), and the OSW_LOG_LEVEL environment variable, which accepts a level name, a level number, or OFF. The environment variable is the only way to pre-empt the notice itself, since it is read before the notice is emitted.
propagate is set to False while the default handler is attached, so callers who have already run logging.basicConfig() do not see every message twice. disable_logging() removes only the handler osw attached itself and restores propagation, which hands control back to the calling application.
Level mapping per call site, from the message content: error for reported failures, warning for retries and skipped or tolerated items, debug for verbose detail and anything behind a debug flag, info for ordinary progress, which is most of them. Messages keep f-string style, matching the existing _logger.error(f"...") at src/osw/core.py:1294.
The parallelize interaction
#154 had just rebuilt output capture in parallelize around ThreadRoutedStdout, which replaces sys.stdout for the duration of a call and gives each worker thread its own buffer, replayed in input order when flush_at_end is set. That works because print() resolves sys.stdout at call time. Converting the workers to logging would have bypassed it completely and silently undone that fix, since a StreamHandler writes to a stream it captured at construction and defaults to stderr.
So this change adds ThreadRoutedLogHandler, the log counterpart, and parallelize now buffers and replays LogRecords alongside the printed text. The stdout router stays, because an external caller passing its own printing function to parallelize should keep working.
One detail worth recording: the routing has to be installed as a handler on the osw logger, not as a filter. A logger's filters run only for records logged on that logger itself, so a filter on osw would never see records from osw.wtsite and the other child loggers, which is exactly the traffic that needs capturing.
Out of scope
tests/, examples/ and scripts/.
Any change to what the messages say. This is a transport change, not an editing pass.
The dask remnants in utils/util.py.
Sequencing
The sweep touches 15 files and conflicts with most of the open PR queue, so it is held until the rest of the queue drains, then rebased onto main.
Activity
LukasGold commentedon Sep 2, 2026
Scope
An AST pass over
src/oswfinds 123print()calls in real code, spread over 15 modules, led bycore.py(29),wtsite.py(23),utils/wikitext.py(17),ontology.py(12) andwiki_tools.py(12). A further 22 sit in theif __name__ == "__main__":demo block ofutils/util.pyand 47 are already commented out. Both of those groups stay as they are.Only two modules use
loggingtoday, and neither consistently:core.py:71has a proper_logger = logging.getLogger(__name__)used for five error calls while the same file still has 29 prints, andutils/workflow.pyimportslogginginside function bodies at lines 195 and 654.Decisions
Logging is on by default.
src/osw/__init__.pyattaches aStreamHandlerto theoswlogger at INFO, writing tosys.stdout, which is where the prints it replaces went. A library that silently swallows its own output would break every existing notebook and script at once, so the default keeps today's behaviour visible.A one-time notice on import says so and says how to change it:
New public API:
osw.set_log_level(level),osw.disable_logging(),osw.enable_logging(level=None, stream=None), and theOSW_LOG_LEVELenvironment variable, which accepts a level name, a level number, orOFF. The environment variable is the only way to pre-empt the notice itself, since it is read before the notice is emitted.propagateis set toFalsewhile the default handler is attached, so callers who have already runlogging.basicConfig()do not see every message twice.disable_logging()removes only the handler osw attached itself and restores propagation, which hands control back to the calling application.Level mapping per call site, from the message content:
errorfor reported failures,warningfor retries and skipped or tolerated items,debugfor verbose detail and anything behind adebugflag,infofor ordinary progress, which is most of them. Messages keep f-string style, matching the existing_logger.error(f"...")atsrc/osw/core.py:1294.The
parallelizeinteraction#154 had just rebuilt output capture in
parallelizearoundThreadRoutedStdout, which replacessys.stdoutfor the duration of a call and gives each worker thread its own buffer, replayed in input order whenflush_at_endis set. That works becauseprint()resolvessys.stdoutat call time. Converting the workers to logging would have bypassed it completely and silently undone that fix, since aStreamHandlerwrites to a stream it captured at construction and defaults to stderr.So this change adds
ThreadRoutedLogHandler, the log counterpart, andparallelizenow buffers and replaysLogRecords alongside the printed text. The stdout router stays, because an external caller passing its own printing function toparallelizeshould keep working.One detail worth recording: the routing has to be installed as a handler on the
oswlogger, not as a filter. A logger's filters run only for records logged on that logger itself, so a filter onoswwould never see records fromosw.wtsiteand the other child loggers, which is exactly the traffic that needs capturing.Out of scope
tests/,examples/andscripts/.daskremnants inutils/util.py.Sequencing
The sweep touches 15 files and conflicts with most of the open PR queue, so it is held until the rest of the queue drains, then rebased onto
main.fix(logging): restore two changes lost in the merge resolution