Skip to content

Commit 1281106

Browse files
authored
ENH: Logging for skypy command line script and Pipeline class (#453)
1 parent 39a9f9d commit 1281106

File tree

9 files changed

+90
-6
lines changed

9 files changed

+90
-6
lines changed

docs/pipeline/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ fits files:
2121
2222
$ skypy examples/galaxies/sdss_photometry.yml sdss_photometry.fits
2323
24+
To view the progress of the pipeline as it runs you can enable logging using the
25+
`--verbose` flag.
26+
2427
Config files are written in YAML format and read using the
2528
`~skypy.pipeline.load_skypy_yaml` funciton. Each entry in the config specifices
2629
an arbitrary variable, but there are also some particular fields that SkyPy uses:

skypy/pipeline/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
dependencies and handle their outputs.
44
"""
55

6+
import logging
7+
8+
log = logging.getLogger(__name__)
9+
610
from ._config import * # noqa
711
from ._pipeline import * # noqa

skypy/pipeline/_items.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'''item types in the pipeline'''
22

33
from collections.abc import Sequence, Mapping
4+
from . import log
45
import inspect
56

67

@@ -88,4 +89,5 @@ def evaluate(self, pipeline):
8889
'''execute the call in the given pipeline'''
8990
args = pipeline.evaluate(self.args)
9091
kwargs = pipeline.evaluate(self.kwargs)
92+
log.info(f"Calling {self.function.__name__}")
9193
return self.function(*args, **kwargs)

skypy/pipeline/_pipeline.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections.abc import Sequence, Mapping
1010
from ._config import load_skypy_yaml
1111
from ._items import Item, Call, Ref
12+
from . import log
1213
import networkx
1314
import pathlib
1415

@@ -164,6 +165,7 @@ def execute(self, parameters={}):
164165

165166
# Initialise cosmology from config parameters
166167
if self.cosmology is not None:
168+
log.info("Setting cosmology")
167169
self.state['cosmology'] = self.evaluate(self.cosmology)
168170

169171
# go through the jobs in dependency order
@@ -172,7 +174,8 @@ def execute(self, parameters={}):
172174
skip = node.get('skip', True)
173175
if skip:
174176
continue
175-
elif job in self.config:
177+
log.info(f"Generating {job}")
178+
if job in self.config:
176179
settings = self.config.get(job)
177180
self.state[job] = self.evaluate(settings)
178181
else:

skypy/pipeline/scripts/skypy.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'''skypy Command Line Script'''
22

33
import argparse
4+
import logging
45
from skypy import __version__ as skypy_version
56
from skypy.pipeline import Pipeline, load_skypy_yaml
67
import sys
@@ -14,21 +15,37 @@ def main(args=None):
1415
parser.add_argument('output', help='Output file name')
1516
parser.add_argument('-o', '--overwrite', action='store_true',
1617
help='Whether to overwrite existing files')
18+
parser.add_argument("-v", "--verbose", action="count", default=0,
19+
help="Increase logging verbosity")
20+
parser.add_argument("-q", "--quiet", action="count", default=0,
21+
help="Decrease logging verbosity")
1722

1823
# get system args if none passed
1924
if args is None:
2025
args = sys.argv[1:]
2126

2227
args = parser.parse_args(args or ['--help'])
23-
config = load_skypy_yaml(args.config)
28+
29+
# Setup skypy logger
30+
default_level = logging._nameToLevel['WARNING']
31+
logging_level = default_level + 10 * (args.quiet - args.verbose)
32+
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
33+
stream_handler = logging.StreamHandler()
34+
stream_handler.setLevel(logging_level)
35+
stream_handler.setFormatter(formatter)
36+
logger = logging.getLogger('skypy')
37+
logger.setLevel(logging_level)
38+
logger.addHandler(stream_handler)
2439

2540
try:
41+
config = load_skypy_yaml(args.config)
2642
pipeline = Pipeline(config)
2743
pipeline.execute()
2844
if args.output:
45+
logger.info(f"Writing {args.output}")
2946
pipeline.write(args.output, overwrite=args.overwrite)
3047
except Exception as e:
31-
print(e)
48+
logger.exception(e)
3249
raise SystemExit(2) from e
3350

3451
return(0)

skypy/pipeline/tests/data/test_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ test_float: 1.0
33
test_str: hello world
44
test_func: !numpy.random.uniform
55
test_func_with_arg: !len 'hello world'
6-
test_cosmology: !astropy.cosmology.FlatLambdaCDM
6+
cosmology: !astropy.cosmology.FlatLambdaCDM
77
H0: 67.74
88
Om0: 0.3075
99
tables:

skypy/pipeline/tests/test_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_load_skypy_yaml():
1818
assert isinstance(config['test_float'], float)
1919
assert isinstance(config['test_str'], str)
2020
assert isinstance(config['test_func'], Call)
21-
assert isinstance(config['test_cosmology'], Call)
21+
assert isinstance(config['cosmology'], Call)
2222
assert isinstance(config['tables']['test_table_1']['test_column_3'], Call)
2323

2424
# Bad function

skypy/pipeline/tests/test_pipeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def test_pipeline_read():
215215
assert isinstance(pipeline['test_int'], int)
216216
assert isinstance(pipeline['test_float'], float)
217217
assert isinstance(pipeline['test_str'], str)
218-
assert isinstance(pipeline['test_cosmology'], Cosmology)
218+
assert isinstance(pipeline['cosmology'], Cosmology)
219219
assert isinstance(pipeline['test_table_1'], Table)
220220
assert isinstance(pipeline['test_table_1']['test_column_3'], Column)
221221

skypy/pipeline/tests/test_skypy.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from astropy.table import Table
12
from astropy.utils.data import get_pkg_data_filename
23
from contextlib import redirect_stdout
34
from io import StringIO
45
import os
56
import pytest
67
from skypy import __version__ as skypy_version
8+
from skypy.pipeline import load_skypy_yaml
9+
from skypy.pipeline._items import Call
710
from skypy.pipeline.scripts import skypy
811

912

@@ -46,8 +49,60 @@ def test_skypy():
4649
assert e.value.code == 2
4750

4851

52+
def test_logging(capsys):
53+
54+
# Run skypy with default verbosity and check log is empty
55+
filename = get_pkg_data_filename('data/test_config.yml')
56+
output_file = 'logging.fits'
57+
skypy.main([filename, output_file])
58+
out, err = capsys.readouterr()
59+
assert(not err)
60+
61+
# Run again with increased verbosity and capture log. Force an exception by
62+
# not using the "--overwrite" flag when the output file already exists.
63+
with pytest.raises(SystemExit):
64+
skypy.main([filename, output_file, '--verbose'])
65+
out, err = capsys.readouterr()
66+
67+
# Determine all DAG jobs and function calls from config
68+
config = load_skypy_yaml(filename)
69+
cosmology = config.pop('cosmology', None)
70+
tables = config.pop('tables', {})
71+
config.update({k: v.pop('.init', Call(Table)) for k, v in tables.items()})
72+
columns = [f'{t}.{c}' for t, cols in tables.items() for c in cols]
73+
functions = [f for f in config.values() if isinstance(f, Call)]
74+
functions += [f for t, cols in tables.items() for f in cols.values() if isinstance(f, Call)]
75+
76+
# Check all jobs appear in the log
77+
for job in list(config) + list(tables) + columns:
78+
log_string = f"[INFO] skypy.pipeline: Generating {job}"
79+
assert(log_string in err)
80+
81+
# Check all functions appear in the log
82+
for f in functions:
83+
log_string = f"[INFO] skypy.pipeline: Calling {f.function.__name__}"
84+
assert(log_string in err)
85+
86+
# Check cosmology appears in the log
87+
if cosmology:
88+
assert("[INFO] skypy.pipeline: Setting cosmology" in err)
89+
90+
# Check writing output file is in the log
91+
assert(f"[INFO] skypy: Writing {output_file}" in err)
92+
93+
# Check error for existing output file is in the log
94+
assert(f"[ERROR] skypy: File '{output_file}' already exists." in err)
95+
96+
# Run again with decreased verbosity and check the log is empty
97+
with pytest.raises(SystemExit):
98+
skypy.main([filename, output_file, '-qq'])
99+
out, err = capsys.readouterr()
100+
assert(not err)
101+
102+
49103
def teardown_module(module):
50104

51105
# Remove fits file generated in test_skypy
52106
os.remove('empty.fits')
107+
os.remove('logging.fits')
53108
os.remove('test.fits')

0 commit comments

Comments
 (0)