Skip to content

Commit e6848a7

Browse files
committed
filter pandas warnings; improve logger
1 parent 268d388 commit e6848a7

4 files changed

Lines changed: 86 additions & 56 deletions

File tree

‎hapiclient/hapitime.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Functions for manipulating HAPI times (restricted ISO 8601 strings)."""
22
import re
33
import time
4+
import warnings
45

56
import pandas
67
import isodate
@@ -268,7 +269,9 @@ def hapitime2datetime(Time, **kwargs):
268269
if pandas_major_version < 2:
269270
Time = pandas.to_datetime(Time, infer_datetime_format=True).tz_convert(tzinfo).to_pydatetime()
270271
else:
271-
Time = pandas.to_datetime(Time).tz_convert(tzinfo).to_pydatetime()
272+
with warnings.catch_warnings():
273+
warnings.filterwarnings('ignore', message='Could not infer format')
274+
Time = pandas.to_datetime(Time).tz_convert(tzinfo).to_pydatetime()
272275
if reshape:
273276
Time = np.reshape(Time, shape)
274277
toc = time.time() - tic

‎hapiclient/log.py‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
_INTERNAL_HANDLER_ATTR = "_hapiclient_internal_handler"
66
_INTERNAL_LEVEL_ATTR = "_hapiclient_internal_level"
77

8-
# Disable propagation by default so hapiclient logs don't bubble up
9-
# to root logger (e.g., pytest's logger). Can be re-enabled by user.
10-
_logger.propagate = False
8+
# Per Python library best practices, add a NullHandler and leave propagation
9+
# enabled so the application controls output. When running under pytest,
10+
# disable propagation to prevent hapiclient INFO messages from leaking into
11+
# pytest's captured log output.
12+
import sys as _sys
13+
_logger.addHandler(_logging.NullHandler())
14+
_logger.propagate = "pytest" not in _sys.modules
15+
del _sys
1116

1217

1318
def configure_logging(logging):
@@ -26,7 +31,7 @@ def configure_logging(logging):
2631
if logging is True:
2732
_logger.setLevel(_logging.INFO)
2833
setattr(_logger, _INTERNAL_LEVEL_ATTR, _logging.INFO)
29-
_logger.propagate = False
34+
_logger.propagate = False # use our own handler when logging=True
3035
_has_internal = any(getattr(h, _INTERNAL_HANDLER_ATTR, False) for h in _logger.handlers)
3136
if not _has_internal:
3237
import sys

‎hapiclient/util.py‎

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -104,80 +104,65 @@ def unicode_error_message(name):
104104
return msg
105105

106106

107-
def warning_test():
108-
"""For testing warning function."""
109-
110-
# Should show warnings in order and only HAPIWarning {1,2} should
111-
# have a different format
112-
from warnings import warn
113-
114-
warn('Normal warning 1')
115-
warn('Normal warning 2')
116-
117-
warning('HAPI Warning 1')
118-
warning('HAPI Warning 2')
119-
120-
warn('Normal warning 3')
121-
warn('Normal warning 4')
107+
class HAPIWarning(Warning):
108+
pass
122109

123110

124111
def warning(*args):
125112
"""Display a short warning message.
126113
127114
warning(message) raises a warning of type HAPIWarning and displays
128-
"Warning: " + message. Use for warnings when a full stack trace is not
115+
"HAPIWarning: " + message. Use for warnings when a full stack trace is not
129116
needed.
130117
"""
131118

119+
import logging
132120
import warnings
133-
from os import path
134-
from sys import stderr
135-
from inspect import stack
121+
import platform
136122

137123
message = args[0]
138-
if len(args) > 1:
139-
fname = args[1]
140-
else:
141-
fname = stack()[1][1]
142124

143-
#line = stack()[1][2]
125+
if logging.getLogger("hapiclient").level == logging.DEBUG:
126+
warnings.warn(message, stacklevel=2)
127+
return
144128

145-
fname = path.basename(fname)
129+
_prefix = "\x1b[31mHAPIWarning:\x1b[0m "
130+
if platform.system() == 'Windows' and pythonshell() == 'shell':
131+
_prefix = "HAPIWarning: "
146132

147-
def prefix():
148-
import platform
149-
prefix = "\x1b[31mHAPIWarning:\x1b[0m "
150-
if platform.system() == 'Windows' and pythonshell() == 'shell':
151-
prefix = "HAPIWarning: "
133+
_orig_formatwarning = warnings.formatwarning
152134

153-
return prefix
135+
def _formatwarning(msg, category, filename, lineno, line=None):
136+
if issubclass(category, HAPIWarning):
137+
return _prefix + str(msg) + "\n"
138+
return _orig_formatwarning(msg, category, filename, lineno, line)
154139

155-
# Custom warning format function
156-
def _warning(message, category=UserWarning, filename='', lineno=-1, file=None, line=''):
157-
if category.__name__ == "HAPIWarning":
158-
stderr.write(prefix() + str(message) + "\n")
159-
else:
160-
# Use default showwarning function.
161-
showwarning_default(message, category=UserWarning,
162-
filename='', lineno=-1,
163-
file=None, line='')
140+
warnings.formatwarning = _formatwarning
141+
try:
142+
warnings.warn(message, HAPIWarning, stacklevel=2)
143+
finally:
144+
warnings.formatwarning = _orig_formatwarning
164145

165-
stderr.flush()
166146

167-
# Reset showwarning function to default
168-
warnings.showwarning = showwarning_default
147+
def warning_test():
148+
"""For testing warning function."""
169149

170-
class HAPIWarning(Warning):
171-
pass
150+
# Should show warnings in order and only HAPIWarning {1,2} should
151+
# have a different format
152+
from warnings import warn
153+
154+
warn('Normal warning 1')
155+
warn('Normal warning 2')
172156

173-
# Copy default showwarning function
174-
showwarning_default = warnings.showwarning
157+
warning('HAPI Warning 1')
158+
warning('HAPI Warning 2')
159+
160+
warn('Normal warning 3')
161+
warn('Normal warning 4')
175162

176-
# Use custom warning function instead of default
177-
warnings.showwarning = _warning
178163

179-
# Raise warning
180-
warnings.warn(message, HAPIWarning)
164+
if __name__ == "__main__":
165+
warning_test()
181166

182167

183168
class HAPIError(Exception):

‎test/test_hapitime2datetime.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def test_parsing():
8484

8585
def test_error_conditions():
8686
from hapiclient import HAPIError
87+
8788
logger.info("test_error_conditions()")
8889

8990
Time = "1999"
@@ -96,7 +97,43 @@ def test_error_conditions():
9697
assert False, "HAPIError not raised for hapitime2datetime(" + str(Time) + ")."
9798

9899

100+
Time = "2001-01-01T00:00:00"
101+
logger.info(" Checking that hapitime2datetime('" + str(Time) + "', allow_missing_Z=False) throws HAPIError")
102+
try:
103+
hapitime2datetime(Time, allow_missing_Z=False)
104+
except HAPIError:
105+
pass
106+
else:
107+
assert False, "HAPIError not raised for hapitime2datetime(" + str(Time) + ", allow_missing_Z=False)."
108+
109+
110+
def test_warning_conditions():
111+
112+
import io
113+
import logging
114+
115+
logger.info("test_warning_conditions()")
116+
117+
stream = io.StringIO()
118+
handler = logging.StreamHandler(stream)
119+
logger2 = logging.getLogger("hapiclient")
120+
logger2.setLevel(logging.DEBUG)
121+
logger2.addHandler(handler)
122+
logger2.propagate = False
123+
124+
try:
125+
Time = "2001-001T00:00:00Z"
126+
hapitime2datetime(Time)
127+
output = stream.getvalue()
128+
assert "Pandas processing failed with error" in output, \
129+
f"Expected 'Pandas processing failed with error' in log output, got:\n{output}"
130+
finally:
131+
logger2.removeHandler(handler)
132+
logger2.propagate = True
133+
134+
99135
if __name__ == '__main__':
100136
test_api()
101137
test_parsing()
102138
test_error_conditions()
139+
test_warning_conditions()

0 commit comments

Comments
 (0)