heatmap_cli 0.25.1__tar.gz → 0.25.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {heatmap_cli-0.25.1/src/heatmap_cli.egg-info → heatmap_cli-0.25.2}/PKG-INFO +1 -1
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/pyproject.toml +1 -1
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli/__init__.py +13 -5
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli/cli.py +38 -13
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli/heatmap.py +11 -2
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2/src/heatmap_cli.egg-info}/PKG-INFO +1 -1
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_animate_by_week.py +13 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_demo_option.py +16 -3
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_heatmap_errors.py +18 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_heatmap_utils.py +28 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_start_date_option.py +20 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/LICENSE.md +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/README.md +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/setup.cfg +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli/__main__.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli.egg-info/SOURCES.txt +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli.egg-info/dependency_links.txt +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli.egg-info/entry_points.txt +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli.egg-info/requires.txt +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/src/heatmap_cli.egg-info/top_level.txt +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_animate_no_frames.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_animate_open.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_annotate_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_annotation_truncation.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_cbar_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_cmap_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_cmax_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_cmin_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_configure_cbar.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_debug_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_end_date_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_env_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_heatmap_direct.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_heatmap_open.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_heatmap_wrapper.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_help_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_iso_53_week_year.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_no_annotate.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_open_heatmap.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_output_dir_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_purge_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_quiet_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_refresh_output_dir.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_setup_logging.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_title_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_verbose_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_version_flag.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_week_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_year_option.py +0 -0
- {heatmap_cli-0.25.1 → heatmap_cli-0.25.2}/tests/test_yes_flag.py +0 -0
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python",
|
|
26
26
|
"Topic :: Scientific/Engineering :: Visualization",
|
|
27
27
|
]
|
|
28
|
-
version = "0.25.
|
|
28
|
+
version = "0.25.2"
|
|
29
29
|
keywords = ["heatmap", "cli", "visualization", "calendar", "data-viz"]
|
|
30
30
|
dependencies = [
|
|
31
31
|
"matplotlib",
|
|
@@ -43,6 +43,10 @@ DEMO_MAX_COUNT = 12000
|
|
|
43
43
|
# Fixed default seed so --demo output is deterministic for docs and tests.
|
|
44
44
|
DEFAULT_DEMO_SEED = 42
|
|
45
45
|
|
|
46
|
+
# The demo sample CSV uses a dedicated, unlikely-to-collide name so --demo
|
|
47
|
+
# does not silently overwrite a user's file in the output directory.
|
|
48
|
+
DEMO_SAMPLE_FILENAME = "heatmap_demo_sample.csv"
|
|
49
|
+
|
|
46
50
|
# Sort colormaps in a case-insensitive manner.
|
|
47
51
|
CMAPS = sorted(plt.colormaps, key=str.casefold)
|
|
48
52
|
|
|
@@ -129,21 +133,24 @@ def generate_demo(config: argparse.Namespace) -> None:
|
|
|
129
133
|
Returns:
|
|
130
134
|
None
|
|
131
135
|
"""
|
|
132
|
-
config.input_filename = f"{config.output_dir}/sample.csv"
|
|
133
136
|
random.seed(config.seed if config.seed is not None else DEFAULT_DEMO_SEED)
|
|
134
137
|
config.cmap = random.sample(CMAPS, config.demo)
|
|
135
|
-
_generate_sample_csv(config)
|
|
138
|
+
config.input_filename = str(_generate_sample_csv(config))
|
|
136
139
|
generate_heatmaps(config)
|
|
137
140
|
|
|
138
141
|
|
|
139
|
-
def _generate_sample_csv(config: argparse.Namespace) ->
|
|
142
|
+
def _generate_sample_csv(config: argparse.Namespace) -> Path:
|
|
140
143
|
"""Generate a sample CSV data file.
|
|
141
144
|
|
|
145
|
+
The file uses the dedicated demo name so a user's own ``sample.csv`` in
|
|
146
|
+
the output directory is never touched. Re-running ``--demo`` into the same
|
|
147
|
+
directory may overwrite this demo's own previous artifact.
|
|
148
|
+
|
|
142
149
|
Args:
|
|
143
150
|
config (argparse.Namespace): Config from command line arguments.
|
|
144
151
|
|
|
145
152
|
Returns:
|
|
146
|
-
|
|
153
|
+
Path: The path the sample CSV was written to.
|
|
147
154
|
"""
|
|
148
155
|
df_dates = pd.DataFrame(
|
|
149
156
|
{
|
|
@@ -155,10 +162,11 @@ def _generate_sample_csv(config: argparse.Namespace) -> None:
|
|
|
155
162
|
)
|
|
156
163
|
df_dates["count"] = random.sample(range(DEMO_MAX_COUNT), len(df_dates))
|
|
157
164
|
|
|
158
|
-
csv_filename = Path(config.output_dir) /
|
|
165
|
+
csv_filename = Path(config.output_dir) / DEMO_SAMPLE_FILENAME
|
|
159
166
|
csv_filename.parent.mkdir(parents=True, exist_ok=True)
|
|
160
167
|
df_dates.to_csv(csv_filename, sep=",", index=False, header=False)
|
|
161
168
|
logger.debug("Generate sample CSV file: %s", csv_filename)
|
|
169
|
+
return csv_filename
|
|
162
170
|
|
|
163
171
|
|
|
164
172
|
class EnvironmentAction(argparse.Action):
|
|
@@ -32,9 +32,7 @@ from typing import TYPE_CHECKING
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
33
|
from collections.abc import Sequence
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
from matplotlib.backend_bases import FigureCanvasBase
|
|
37
|
-
from matplotlib.backends import backend_registry
|
|
35
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
38
36
|
|
|
39
37
|
from heatmap_cli import (
|
|
40
38
|
CMAPS,
|
|
@@ -44,18 +42,31 @@ from heatmap_cli import (
|
|
|
44
42
|
generate_demo,
|
|
45
43
|
setup_logging,
|
|
46
44
|
)
|
|
47
|
-
from heatmap_cli.heatmap import
|
|
45
|
+
from heatmap_cli.heatmap import (
|
|
46
|
+
_weeks_in_iso_year,
|
|
47
|
+
)
|
|
48
|
+
from heatmap_cli.heatmap import (
|
|
49
|
+
run as generate_heatmaps,
|
|
50
|
+
)
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
def _get_supported_formats() -> list[str]:
|
|
51
|
-
"""Retrieve
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
"""Retrieve image formats supported by the Agg rendering backend.
|
|
55
|
+
|
|
56
|
+
All heatmaps are rendered with the Agg backend (see
|
|
57
|
+
``heatmap.run`` and ``_setup_plot_artists``), so the CLI choices are
|
|
58
|
+
derived from `FigureCanvasAgg` rather than the backend active at import
|
|
59
|
+
time, keeping ``--format`` choices in sync with what can actually be
|
|
60
|
+
saved.
|
|
61
|
+
|
|
62
|
+
``gif`` is added explicitly: it is not produced by matplotlib's
|
|
63
|
+
``savefig`` (Agg does not list it) but by the PIL encoder in the
|
|
64
|
+
``--animate-by-week`` path, which implies that format.
|
|
65
|
+
"""
|
|
66
|
+
formats = sorted(FigureCanvasAgg.get_supported_filetypes().keys())
|
|
67
|
+
if "gif" not in formats:
|
|
68
|
+
formats.append("gif")
|
|
69
|
+
return formats
|
|
59
70
|
|
|
60
71
|
|
|
61
72
|
IMAGE_FORMATS = _get_supported_formats()
|
|
@@ -429,11 +440,19 @@ def _process_args(
|
|
|
429
440
|
Raises:
|
|
430
441
|
SystemExit: If validation fails, typically via parser.error().
|
|
431
442
|
"""
|
|
432
|
-
#
|
|
443
|
+
# A --start-date/--end-date pair defines a self-contained calendar date
|
|
444
|
+
# range that overrides the -y/--year and -w/--week cutoff, avoiding a
|
|
445
|
+
# double filter between the ISO-week year column and the raw date column.
|
|
446
|
+
# When only one endpoint is given it derives a partial-year view; when
|
|
447
|
+
# both are given --end-date wins for the year/week since it caps the range.
|
|
433
448
|
if parsed_args.end_date:
|
|
434
449
|
(year, week, _day) = parsed_args.end_date.isocalendar()
|
|
435
450
|
parsed_args.year = [year]
|
|
436
451
|
parsed_args.week = week
|
|
452
|
+
elif parsed_args.start_date:
|
|
453
|
+
(year, _week, _day) = parsed_args.start_date.isocalendar()
|
|
454
|
+
parsed_args.year = [year]
|
|
455
|
+
parsed_args.week = _weeks_in_iso_year(year)
|
|
437
456
|
|
|
438
457
|
if (
|
|
439
458
|
parsed_args.start_date
|
|
@@ -460,6 +479,12 @@ def _process_args(
|
|
|
460
479
|
if parsed_args.cmap is None:
|
|
461
480
|
parsed_args.cmap = [DEFAULT_CMAP]
|
|
462
481
|
|
|
482
|
+
# A GIF output is only ever produced by the animation path, so asking for
|
|
483
|
+
# gif implies --animate-by-week; otherwise fig.savefig(format="gif") would
|
|
484
|
+
# emit a single-frame still instead of an animation.
|
|
485
|
+
if parsed_args.format == "gif":
|
|
486
|
+
parsed_args.animate_by_week = True
|
|
487
|
+
|
|
463
488
|
return parsed_args
|
|
464
489
|
|
|
465
490
|
|
|
@@ -311,7 +311,8 @@ def _read_and_prepare_data(config: argparse.Namespace) -> pd.DataFrame:
|
|
|
311
311
|
|
|
312
312
|
Raises:
|
|
313
313
|
FileNotFoundError: If the input CSV file does not exist.
|
|
314
|
-
ValueError: If duplicate dates or negative
|
|
314
|
+
ValueError: If the input is empty, or duplicate dates or negative
|
|
315
|
+
counts are found.
|
|
315
316
|
"""
|
|
316
317
|
try:
|
|
317
318
|
dataframe = pd.read_csv(
|
|
@@ -323,6 +324,10 @@ def _read_and_prepare_data(config: argparse.Namespace) -> pd.DataFrame:
|
|
|
323
324
|
logger.error("Input file not found: %s", config.input_filename)
|
|
324
325
|
raise
|
|
325
326
|
|
|
327
|
+
if dataframe.empty:
|
|
328
|
+
msg = f"Input CSV file is empty: {config.input_filename}"
|
|
329
|
+
raise ValueError(msg)
|
|
330
|
+
|
|
326
331
|
dataframe["date"] = pd.to_datetime(dataframe["date"])
|
|
327
332
|
|
|
328
333
|
negative_counts = dataframe[dataframe["count"] < 0]
|
|
@@ -814,13 +819,17 @@ def _refresh_output_dir(config: argparse.Namespace) -> None:
|
|
|
814
819
|
def _get_output_dir(config: argparse.Namespace) -> Path:
|
|
815
820
|
"""Get the output directory path, resolved relative to CWD.
|
|
816
821
|
|
|
822
|
+
Expands a leading ``~``/``~user`` (user home) and environment variables
|
|
823
|
+
such as ``$HOME`` or ``${OUTPUT_DIR}`` before resolving relative paths
|
|
824
|
+
against the current working directory.
|
|
825
|
+
|
|
817
826
|
Args:
|
|
818
827
|
config (argparse.Namespace): Config from command line arguments.
|
|
819
828
|
|
|
820
829
|
Returns:
|
|
821
830
|
Path: The output directory path.
|
|
822
831
|
"""
|
|
823
|
-
output_dir = Path(config.output_dir)
|
|
832
|
+
output_dir = Path(os.path.expandvars(config.output_dir)).expanduser()
|
|
824
833
|
if output_dir.is_absolute():
|
|
825
834
|
return output_dir
|
|
826
835
|
|
|
@@ -52,3 +52,16 @@ def test_animate_by_week_with_start_date(cli_runner, csv_file):
|
|
|
52
52
|
"2023-01-01",
|
|
53
53
|
)
|
|
54
54
|
assert ret.returncode == 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_gif_format_implies_animate_by_week(cli_runner, csv_file, tmpdir):
|
|
58
|
+
"""-f gif should route into the animation path via --animate-by-week."""
|
|
59
|
+
csv = csv_file("sample.csv")
|
|
60
|
+
ret = cli_runner(csv, "-f", "gif", "-y", "2023", "-w", "1", "-d")
|
|
61
|
+
assert ret.returncode == 0
|
|
62
|
+
assert "animate_by_week=True" in ret.stderr
|
|
63
|
+
|
|
64
|
+
output_dir = Path(str(tmpdir.join("scripttest", "output")))
|
|
65
|
+
gif_files = list(output_dir.glob("*.gif"))
|
|
66
|
+
assert len(gif_files) == 1
|
|
67
|
+
assert "animated" in gif_files[0].name
|
|
@@ -17,13 +17,13 @@ import argparse
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from unittest.mock import MagicMock
|
|
19
19
|
|
|
20
|
-
from heatmap_cli import CMAPS, DemoAction
|
|
20
|
+
from heatmap_cli import CMAPS, DEMO_SAMPLE_FILENAME, DemoAction
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def test_debug_logs(cli_runner):
|
|
24
24
|
ret = cli_runner("-d", "--demo", 1)
|
|
25
25
|
assert "demo=1" in ret.stderr
|
|
26
|
-
assert "input_filename='output/
|
|
26
|
+
assert f"input_filename='output/{DEMO_SAMPLE_FILENAME}'" in ret.stderr
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def test_no_debug_logs_without_debug_flag(cli_runner):
|
|
@@ -64,7 +64,7 @@ def test_demo_clamps_to_minimum_of_one():
|
|
|
64
64
|
def _demo_output(tmpdir):
|
|
65
65
|
output_dir = Path(tmpdir, "scripttest", "output")
|
|
66
66
|
files = sorted(path.name for path in output_dir.iterdir())
|
|
67
|
-
csv_bytes = (output_dir /
|
|
67
|
+
csv_bytes = (output_dir / DEMO_SAMPLE_FILENAME).read_bytes()
|
|
68
68
|
return files, csv_bytes
|
|
69
69
|
|
|
70
70
|
|
|
@@ -87,3 +87,16 @@ def test_demo_changes_with_different_seed(cli_runner, tmpdir):
|
|
|
87
87
|
_, csv2 = _demo_output(tmpdir)
|
|
88
88
|
|
|
89
89
|
assert csv1 != csv2
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_demo_does_not_clobber_user_sample(tmpdir, cli_runner):
|
|
93
|
+
"""A user's own sample.csv in the output dir must survive --demo."""
|
|
94
|
+
output_dir = Path(tmpdir, "scripttest", "output")
|
|
95
|
+
output_dir.mkdir(parents=True)
|
|
96
|
+
user_sample = output_dir / "sample.csv"
|
|
97
|
+
user_sample.write_text("user,data\n")
|
|
98
|
+
|
|
99
|
+
cli_runner("--demo", 1)
|
|
100
|
+
|
|
101
|
+
assert user_sample.read_text() == "user,data\n"
|
|
102
|
+
assert (output_dir / DEMO_SAMPLE_FILENAME).exists()
|
|
@@ -56,6 +56,24 @@ def test_negative_count_error_direct(tmpdir):
|
|
|
56
56
|
_read_and_prepare_data(config)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def test_empty_csv_error_direct(tmpdir):
|
|
60
|
+
csv_path = Path(tmpdir) / "empty.csv"
|
|
61
|
+
csv_path.write_text("")
|
|
62
|
+
|
|
63
|
+
config = argparse.Namespace(input_filename=str(csv_path), annotate=False)
|
|
64
|
+
with pytest.raises(ValueError, match="Input CSV file is empty"):
|
|
65
|
+
_read_and_prepare_data(config)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_empty_csv_error_cli(cli_runner, tmpdir):
|
|
69
|
+
csv_path = Path(tmpdir) / "empty.csv"
|
|
70
|
+
csv_path.write_text("")
|
|
71
|
+
|
|
72
|
+
ret = cli_runner(str(csv_path))
|
|
73
|
+
assert "Input CSV file is empty" in ret.stderr
|
|
74
|
+
assert "No data extracted" not in ret.stderr
|
|
75
|
+
|
|
76
|
+
|
|
59
77
|
def test_refresh_output_dir_purge_os_error(tmpdir):
|
|
60
78
|
output_dir = Path(tmpdir) / "output"
|
|
61
79
|
output_dir.mkdir()
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
import argparse
|
|
17
17
|
import datetime
|
|
18
|
+
from pathlib import Path
|
|
18
19
|
|
|
19
20
|
from heatmap_cli.heatmap import (
|
|
20
21
|
_generate_filename,
|
|
21
22
|
_generate_title,
|
|
23
|
+
_get_output_dir,
|
|
22
24
|
_sanitize_string_for_filename,
|
|
23
25
|
_week_start_date,
|
|
24
26
|
)
|
|
@@ -82,3 +84,29 @@ def test_generate_title():
|
|
|
82
84
|
|
|
83
85
|
config.title = "Custom Title"
|
|
84
86
|
assert _generate_title(config) == "Custom Title"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_get_output_dir_relative(tmpdir, monkeypatch):
|
|
90
|
+
monkeypatch.chdir(tmpdir)
|
|
91
|
+
config = argparse.Namespace(output_dir="out")
|
|
92
|
+
assert _get_output_dir(config) == Path.cwd() / "out"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_get_output_dir_absolute(tmpdir):
|
|
96
|
+
absolute = Path(str(tmpdir)) / "abs"
|
|
97
|
+
config = argparse.Namespace(output_dir=str(absolute))
|
|
98
|
+
assert _get_output_dir(config) == absolute
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_get_output_dir_expands_tilde(tmpdir, monkeypatch):
|
|
102
|
+
home = Path(str(tmpdir))
|
|
103
|
+
monkeypatch.setenv("HOME", str(home))
|
|
104
|
+
config = argparse.Namespace(output_dir="~/out")
|
|
105
|
+
assert _get_output_dir(config) == home / "out"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_get_output_dir_expands_env_var(tmpdir, monkeypatch):
|
|
109
|
+
fromenv = Path(str(tmpdir)) / "fromenv"
|
|
110
|
+
monkeypatch.setenv("OUTPUT_DIR", str(fromenv))
|
|
111
|
+
config = argparse.Namespace(output_dir="$OUTPUT_DIR/sub")
|
|
112
|
+
assert _get_output_dir(config) == fromenv / "sub"
|
|
@@ -56,3 +56,23 @@ def test_title_with_start_and_end_date(cli_runner, csv_file):
|
|
|
56
56
|
"Year 2023: Total Daily Walking Steps From 2023-01-01 to 2023-01-31"
|
|
57
57
|
)
|
|
58
58
|
assert f"DEBUG: heatmap_cli.heatmap: {expected_title}" in ret.stderr
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_start_date_alone_derives_year(cli_runner, csv_file):
|
|
62
|
+
"""--start-date alone should derive year/week instead of double-filtering.
|
|
63
|
+
|
|
64
|
+
Without this, -s 2023-06-01 on 2023 data would default year to the
|
|
65
|
+
current year and raise "No data extracted". The date now drives a
|
|
66
|
+
partial-year view.
|
|
67
|
+
"""
|
|
68
|
+
csv = csv_file("sample.csv")
|
|
69
|
+
ret = cli_runner(csv, "-d", "-s", "2023-06-01")
|
|
70
|
+
assert "year=[2023], week=52" in ret.stderr
|
|
71
|
+
assert ret.returncode == 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_start_date_overrides_year_cutoff(cli_runner, csv_file):
|
|
75
|
+
"""An explicit -y conflicting with --start-date is resolved by the date."""
|
|
76
|
+
csv = csv_file("sample.csv")
|
|
77
|
+
ret = cli_runner(csv, "-d", "-s", "2023-06-01", "--year", "2024")
|
|
78
|
+
assert "year=[2023]" in ret.stderr
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|