Skip to content

Commit 387cd8f

Browse files
committed
Refactor coverage configuration and enhance CLI functionality
- Remove obsolete .coveragerc file and integrate coverage settings into pyproject.toml - Update CLI main function to accept command line arguments and improve docstring - Add tests for CLI functionality to ensure proper argument handling - Enhance exception handling in planetarypy.exceptions with additional docstrings - Introduce new tests for PDS index configuration and Spicer classes to improve test coverage
1 parent eae1794 commit 387cd8f

File tree

10 files changed

+307
-126
lines changed

10 files changed

+307
-126
lines changed

.coveragerc

Lines changed: 0 additions & 11 deletions
This file was deleted.

pyproject.toml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ spice = [
4949
dev = [
5050
"pytest",
5151
"pytest-cov",
52+
"pytest-xdist", # For parallel test execution
5253
"build",
5354
"twine",
5455
"pip-tools",
55-
"make",
5656
]
5757

5858
[project.scripts]
@@ -72,8 +72,23 @@ include = [
7272
"HISTORY.rst",
7373
]
7474

75-
[tool.pytest]
75+
[tool.pytest.ini_options]
7676
testpaths = ["tests"]
77+
addopts = "-xvs --import-mode=importlib --cov=planetarypy --cov-report=term --cov-report=html -n auto"
78+
79+
[tool.coverage.run]
80+
source = ["planetarypy"]
81+
omit = ["*/tests/*"]
82+
83+
[tool.coverage.report]
84+
exclude_lines = [
85+
"pragma: no cover",
86+
"def __repr__",
87+
"raise NotImplementedError",
88+
"if __name__ == .__main__.:",
89+
"pass",
90+
"raise ImportError",
91+
]
7792

7893
[tool.flake8]
7994
exclude = ["docs"]

src/planetarypy/cli.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
"""Console script for planetarypy."""
2+
23
import argparse
34
import sys
45

56

6-
def main():
7-
"""Console script for planetarypy."""
7+
def main(args=None):
8+
"""Console script for planetarypy.
9+
10+
Args:
11+
args: List of command line arguments. If None, sys.argv[1:] is used.
12+
13+
Returns:
14+
int: Return code
15+
"""
816
parser = argparse.ArgumentParser()
9-
parser.add_argument('_', nargs='*')
10-
args = parser.parse_args()
17+
parser.add_argument("_", nargs="*")
18+
args = parser.parse_args(args)
1119

1220
print("Arguments: " + str(args._))
13-
print("Replace this message by putting your code into "
14-
"planetarypy.cli.main")
21+
print("Replace this message by putting your code into planetarypy.cli.main")
1522
return 0
1623

1724

src/planetarypy/exceptions.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
"""PlanetarPy exceptions."""
22

3-
__all__ = ['Error', 'SomethingNotSetError', 'ProjectionNotSetError', 'GeoTransformNotSetError', 'SpicerError',
4-
'SPointNotSetError', 'ObserverNotSetError', 'SpiceError', 'MissingParameterError']
3+
__all__ = [
4+
"Error",
5+
"SomethingNotSetError",
6+
"ProjectionNotSetError",
7+
"GeoTransformNotSetError",
8+
"SpicerError",
9+
"SPointNotSetError",
10+
"ObserverNotSetError",
11+
"SpiceError",
12+
"MissingParameterError",
13+
]
514

615

716
class Error(Exception):
817
"""Base class for exceptions in this module."""
18+
919
pass
1020

1121

@@ -26,20 +36,26 @@ def __str__(self):
2636

2737

2838
class ProjectionNotSetError(SomethingNotSetError):
29-
what = "Projection"
39+
"""Exception raised when a projection is not set."""
40+
41+
def __init__(self, where):
42+
super().__init__(where, "Projection")
3043

3144

3245
class GeoTransformNotSetError(SomethingNotSetError):
33-
what = "GeoTransform"
46+
"""Exception raised when a GeoTransform is not set."""
47+
48+
def __init__(self, where):
49+
super().__init__(where, "GeoTransform")
3450

3551

3652
class SpicerError(Exception):
3753
"""Base class for exceptions in this module."""
54+
3855
pass
3956

4057

4158
class SPointNotSetError(SpicerError):
42-
4359
def __init__(self, txt):
4460
self.txt = txt
4561

@@ -50,14 +66,12 @@ def __str__(self):
5066

5167

5268
class ObserverNotSetError(SpicerError):
53-
5469
def __str__(self):
5570
return """The method you called requires an observer to be set.
5671
This operation had no effect."""
5772

5873

5974
class SpiceError(SpicerError):
60-
6175
def __init__(self, function):
6276
self.function = function
6377

@@ -66,7 +80,6 @@ def __str__(self):
6680

6781

6882
class MissingParameterError(SpicerError):
69-
7083
def __init__(self, txt):
7184
self.txt = txt
7285

src/planetarypy/pds/index_labels.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
parsing index files, and converting them to convenient data structures.
55
"""
66

7+
# Temporarily suppress PendingDeprecationWarning from pvl.collections.Units
8+
# This can be removed once pvl version > 1.3.2 is used
9+
import warnings
10+
11+
warnings.filterwarnings("ignore", category=PendingDeprecationWarning, module="pvl")
12+
713
__all__ = [
814
"PVLColumn",
915
"IndexLabel",
@@ -13,7 +19,6 @@
1319
"find_mixed_type_cols",
1420
]
1521

16-
import warnings
1722
from typing import Union
1823

1924
import pandas as pd

tests/test_cli.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import pytest
2+
from planetarypy.cli import main
3+
4+
def test_main_help(capsys):
5+
"""Test the CLI help output."""
6+
with pytest.raises(SystemExit) as pytest_wrapped_e:
7+
main(['-h'])
8+
captured = capsys.readouterr()
9+
assert 'usage:' in captured.out
10+
assert pytest_wrapped_e.value.code == 0
11+
12+
def test_main_default():
13+
"""Test the CLI with default arguments."""
14+
result = main([])
15+
assert result == 0
16+
17+
def test_main_with_args():
18+
"""Test the CLI with some arguments."""
19+
result = main(['arg1', 'arg2'])
20+
assert result == 0

tests/test_config.py

Lines changed: 20 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,61 @@
1-
"""Tests for config module."""
1+
"""Tests for basic configuration module."""
22

3-
import pytest
4-
from pathlib import Path
53
import tempfile
6-
from planetarypy.config import config, Config, reset_non_urls
4+
from pathlib import Path
5+
6+
from planetarypy.config import Config, config
77

88

99
def test_config_loading():
10+
"""Test that the config is properly loaded."""
1011
assert isinstance(config.d, dict)
11-
assert "missions" in config.d
12-
13-
14-
def test_config_missions():
15-
assert "cassini" in config["missions"]
16-
assert "iss" in config["missions"]["cassini"]
12+
assert isinstance(config.storage_root, Path)
1713

1814

1915
def test_config_getitem():
20-
# Test dictionary-like access
21-
assert config["missions"] is not None
22-
assert isinstance(config["missions"], dict)
16+
"""Test dictionary-like access to config values."""
17+
result = config["storage_root"]
18+
assert isinstance(result, str)
19+
assert len(result) > 0
2320

2421

2522
def test_config_missing_key():
26-
# Test that missing keys return empty string
23+
"""Test that missing keys return empty string."""
2724
assert config.get_value("nonexistent.key") == ""
2825

2926

30-
def test_config_nested_access():
31-
cassini_config = config["missions"]["cassini"]
32-
assert isinstance(cassini_config, dict)
33-
assert "iss" in cassini_config
34-
35-
3627
def test_config_get_value():
28+
"""Test retrieving values with get_value method."""
3729
# Test with valid keys
38-
assert config.get_value("cassini.iss") != ""
30+
assert config.get_value("storage_root") != ""
3931
# Test with invalid key
4032
assert config.get_value("nonexistent.key") == ""
41-
# Test with full path
42-
assert config.get_value("missions.cassini.iss") != ""
4333

4434

4535
def test_config_set_value():
36+
"""Test setting values in the config."""
4637
with tempfile.TemporaryDirectory() as tmpdir:
4738
temp_config = Config(Path(tmpdir) / "test_config.toml")
48-
49-
# Initialize the full path structure
50-
temp_config.tomldoc["missions"] = {"test": {"instrument": {}}}
51-
temp_config.save()
5239

5340
# Test setting a new value
54-
temp_config.set_value("missions.test.instrument.value", "test_value")
55-
assert temp_config.get_value("test.instrument.value") == "test_value"
41+
temp_config.set_value("test.nested.value", "test_value")
42+
assert temp_config.get_value("test.nested.value") == "test_value"
5643

5744
# Test overwriting an existing value
58-
temp_config.set_value("missions.test.instrument.value", "new_value")
59-
assert temp_config.get_value("test.instrument.value") == "new_value"
60-
61-
62-
def test_config_list_instruments():
63-
instruments = config.list_instruments("cassini")
64-
assert isinstance(instruments, list)
65-
assert "iss" in instruments
66-
67-
68-
def test_config_get_datalevels():
69-
datalevels = config.get_datalevels("cassini.iss")
70-
assert isinstance(datalevels, list)
71-
72-
73-
def test_config_list_indexes():
74-
indexes = config.list_indexes("cassini.iss")
75-
assert isinstance(indexes, list)
76-
77-
# Test with missions prefix
78-
indexes = config.list_indexes("missions.cassini.iss")
79-
assert isinstance(indexes, list)
45+
temp_config.set_value("test.nested.value", "new_value")
46+
assert temp_config.get_value("test.nested.value") == "new_value"
8047

8148

8249
def test_config_storage_root():
50+
"""Test that storage_root is properly configured."""
8351
assert config.storage_root.is_dir()
8452
assert isinstance(config.storage_root, Path)
8553

8654

87-
88-
def test_reset_non_urls():
89-
test_dict = {
90-
"url": "http://example.com", # Should be preserved (key contains 'url')
91-
"regular_key": "value", # Should be reset
92-
"nested": {
93-
"download_url": "http://test.com", # Should be preserved
94-
"data_field": "other_value" # Should be reset
95-
},
96-
}
97-
98-
# Create a deep copy to avoid modifying the original
99-
import copy
100-
test_dict_copy = copy.deepcopy(test_dict)
101-
reset_dict = reset_non_urls(test_dict_copy, "")
102-
103-
# Check that URL fields are preserved
104-
assert reset_dict["url"] == "http://example.com"
105-
assert reset_dict["nested"]["download_url"] == "http://test.com"
106-
107-
# Check that non-URL fields are reset
108-
assert reset_dict["regular_key"] == ""
109-
assert reset_dict["nested"]["data_field"] == ""
110-
111-
# Verify original dict wasn't modified
112-
assert test_dict["regular_key"] == "value"
113-
assert test_dict["nested"]["data_field"] == "other_value"
114-
115-
11655
def test_config_custom_path():
56+
"""Test creating a config with custom path."""
11757
with tempfile.TemporaryDirectory() as tmpdir:
11858
custom_path = Path(tmpdir) / "custom_config.toml"
11959
custom_config = Config(custom_path)
12060
assert custom_config.path == custom_path
12161
assert custom_path.exists()
122-
123-
124-
def test_config_missions_property():
125-
missions = config.missions
126-
assert isinstance(missions, list)
127-
assert "cassini" in missions
128-
129-
130-
@pytest.mark.parametrize(
131-
"mission,expected_instrument",
132-
[
133-
("cassini", "iss"),
134-
# Add more mission/instrument pairs as needed
135-
],
136-
)
137-
def test_config_instruments_for_mission(mission, expected_instrument):
138-
instruments = config.list_instruments(mission)
139-
assert expected_instrument in instruments

tests/test_exceptions.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from planetarypy.exceptions import (
3+
Error, SomethingNotSetError, ProjectionNotSetError,
4+
GeoTransformNotSetError, SpicerError, SPointNotSetError,
5+
ObserverNotSetError, SpiceError, MissingParameterError
6+
)
7+
8+
def test_base_error():
9+
"""Test base Error class."""
10+
with pytest.raises(Error):
11+
raise Error()
12+
13+
def test_something_not_set_error():
14+
"""Test SomethingNotSetError formatting."""
15+
error = SomethingNotSetError("location", "parameter")
16+
assert str(error) == "parameter not set in location"
17+
18+
def test_projection_not_set_error():
19+
"""Test ProjectionNotSetError."""
20+
error = ProjectionNotSetError("location")
21+
assert "Projection" in str(error)
22+
23+
def test_spoint_not_set_error():
24+
"""Test SPointNotSetError."""
25+
error = SPointNotSetError("test message")
26+
assert "spoint" in str(error).lower()
27+
28+
def test_missing_parameter_error():
29+
"""Test MissingParameterError."""
30+
error = MissingParameterError("required_param")
31+
assert "required_param" in str(error)
32+
33+
def test_geotransform_not_set_error():
34+
"""Test GeoTransformNotSetError."""
35+
error = GeoTransformNotSetError("location")
36+
assert "GeoTransform" in str(error)

0 commit comments

Comments
 (0)