deepcode-ci-bot[bot]: [**DeepCode Report (#e1cf57)**](https://www.deepcode.ai/app/gh/hugovk/pypistats/65349dea85ff97c329310cb2044cef11a5403ad1/hugovk/pypistats/e1cf57c309ec668c7a9afa94bad162d0e6d43ac8/_/%2F/code/)
---
DeepCode analyzed this pull request.
There are 2 new info reports. 
diff --git a/src/pypistats/__init__.py b/src/pypistats/__init__.py
index 36f5a49..f184ce2 100644
--- a/src/pypistats/__init__.py
+++ b/src/pypistats/__init__.py
@@ -125,9 +125,16 @@ def pypi_stats_api(
 
         _save_cache(cache_file, res)
 
+    first, last = _date_range(res["data"])
+
     if start_date or end_date:
         res["data"] = _filter(res["data"], start_date, end_date)
 
+    if start_date:
+        first = start_date
+    if end_date:
+        last = end_date
+
     if total == "monthly":
         res["data"] = _monthly_total(res["data"])
     elif total == "all":
@@ -144,7 +151,12 @@ def pypi_stats_api(
     data = _percent(data)
     data = _grand_total(data)
 
-    return _tabulate(data, format)
+    output = _tabulate(data, format)
+
+    if first:
+        return f"{output}\nDate range: {first} - {last}\n"
+    else:
+        return output
 
 
 def _filter(data, start_date=None, end_date=None):
@@ -223,6 +235,24 @@ def _total(data):
     return data
 
 
+def _date_range(data):
+    """Return the first and last dates in data"""
+    try:
+        first = data[0]["date"]
+        last = data[0]["date"]
+    except KeyError:
+        # /recent has no dates
+        return None, None
+    for row in data:
+        date = row["date"]
+        if date < first:
+            first = date
+        elif date > last:
+            last = date
+
+    return first, last
+
+
 def _grand_total(data):
     """Add a grand total row"""
 
diff --git a/tox.ini b/tox.ini
index df0617c..26fbb8b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,7 +11,7 @@ deps =
     requests_mock
 commands =
     # Unit tests
-    {envpython} -m pytest --cov pypistats --cov tests {posargs}
+    {envpython} -m pytest --cov pypistats {posargs}
 
     # Test runs
     pypistats --version

Some functional question
When I wrote the code as `pypistats overall pandas`,  is that means the number of downloading pandas from PyPi in the history?
diff --git a/tests/test_pypistats.py b/tests/test_pypistats.py
index 0977f12..1fa00f3 100644
--- a/tests/test_pypistats.py
+++ b/tests/test_pypistats.py
@@ -35,6 +35,14 @@ SAMPLE_DATA_VERSION_STRINGS = [
     {"category": "3.1", "date": "2018-08-15", "downloads": 10},
     {"category": "3.10", "date": "2018-08-15", "downloads": 1},
 ]
+SAMPLE_RESPONSE_OVERALL = """{
+          "data": [
+            {"category": "without_mirrors", "date": "2018-11-01", "downloads": 2295765},
+            {"category": "without_mirrors", "date": "2018-11-02", "downloads": 2297591}
+          ],
+          "package": "pip",
+          "type": "overall_downloads"
+        }"""
 
 
 def stub__cache_filename(*args):
@@ -380,6 +388,35 @@ class TestPypiStats(unittest.TestCase):
         # Assert
         self.assertEqual(output, SAMPLE_DATA_RECENT)
 
+    def test__date_range(self):
+        # Arrange
+        data = copy.deepcopy(PYTHON_MINOR_DATA)
+
+        # Act
+        first, last = pypistats._date_range(data)
+
+        # Assert
+        self.assertEqual(first, "2018-04-16")
+        self.assertEqual(last, "2018-09-23")
+
+    def test__date_range_no_dates_in_data(self):
+        # Arrange
+        # recent
+        data = [
+            {
+                "data": {"last_day": 70, "last_month": 445, "last_week": 268},
+                "package": "dapy",
+                "type": "recent_downloads",
+            }
+        ]
+
+        # Act
+        first, last = pypistats._date_range(data)
+
+        # Assert
+        self.assertIsNone(first)
+        self.assertIsNone(last)
+
     def test__grand_total(self):
         # Arrange
         data = copy.deepcopy(PYTHON_MINOR_DATA)
@@ -499,18 +536,13 @@ class TestPypiStats(unittest.TestCase):
         # Arrange
         package = "pip"
         mocked_url = "https://pypistats.org/api/packages/pip/overall"
-        mocked_response = """{
-          "data": [
-            {"category": "without_mirrors", "date": "2018-11-01", "downloads": 2295765},
-            {"category": "without_mirrors", "date": "2018-11-02", "downloads": 2297591}
-          ],
-          "package": "pip",
-          "type": "overall_downloads"
-        }"""
+        mocked_response = SAMPLE_RESPONSE_OVERALL
         expected_output = """
 |    category     | downloads |
 |-----------------|----------:|
 | without_mirrors | 2,297,591 |
+
+Date range: 2018-11-02 - 2018-11-02
 """
 
         # Act
@@ -521,6 +553,27 @@ class TestPypiStats(unittest.TestCase):
         # Assert
         self.assertEqual(output.strip(), expected_output.strip())
 
+    def test_overall_tabular_end_date(self):
+        # Arrange
+        package = "pip"
+        mocked_url = "https://pypistats.org/api/packages/pip/overall"
+        mocked_response = SAMPLE_RESPONSE_OVERALL
+        expected_output = """
+|    category     | downloads |
+|-----------------|----------:|
+| without_mirrors | 2,295,765 |
+
+Date range: 2018-11-01 - 2018-11-01
+"""
+
+        # Act
+        with requests_mock.Mocker() as m:
+            m.get(mocked_url, text=mocked_response)
+            output = pypistats.overall(package, mirrors=False, end_date="2018-11-01")
+
+        # Assert
+        self.assertEqual(output.strip(), expected_output.strip())
+
     def test_python_major_json(self):
         # Arrange
         package = "pip"
diff --git a/tests/test_pypistats_cache.py b/tests/test_pypistats_cache.py
index 9d61c32..1edea97 100644
--- a/tests/test_pypistats_cache.py
+++ b/tests/test_pypistats_cache.py
@@ -102,6 +102,8 @@ class TestPypiStatsCache(unittest.TestCase):
 |    category     | downloads |
 |-----------------|----------:|
 | without_mirrors | 2,295,765 |
+
+Date range: 2018-11-01 - 2018-11-01
 """
 
         # Act

pip install -e .
parse_log_pytest
pytest pytest-cov requests_mock freezegun flake8 black
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
black==25.1.0
certifi==2025.1.31
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
DataProperty==1.1.0
dominate==2.9.1
exceptiongroup==1.2.2
flake8==7.2.0
freezegun==1.5.1
idna==3.10
iniconfig==2.1.0
mbstrdecoder==1.1.4
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==24.2
pathspec==0.12.1
pathvalidate==3.2.3
platformdirs==4.3.7
pluggy==1.5.0
pycodestyle==2.13.0
pyflakes==3.3.2
-e git+https://github.com/hugovk/pypistats.git@d2cb1f12aa5edde24dc31bba5b86ad118d3cf4b0#egg=pypistats
pytablewriter==1.2.1
pytest==8.3.5
pytest-cov==6.0.0
python-dateutil==2.9.0.post0
python-slugify==8.0.4
pytz==2025.2
requests==2.32.3
requests-mock==1.12.1
six==1.17.0
tabledata==1.3.4
tcolorpy==0.1.7
text-unidecode==1.3
tomli==2.2.1
typepy==1.3.4
typing_extensions==4.13.0
urllib3==2.3.0

name: pypistats
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - black==25.1.0
      - certifi==2025.1.31
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - dataproperty==1.1.0
      - dominate==2.9.1
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - freezegun==1.5.1
      - idna==3.10
      - iniconfig==2.1.0
      - mbstrdecoder==1.1.4
      - mccabe==0.7.0
      - mypy-extensions==1.0.0
      - packaging==24.2
      - pathspec==0.12.1
      - pathvalidate==3.2.3
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.2
      - pypistats==0.7.2.dev18
      - pytablewriter==1.2.1
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - python-dateutil==2.9.0.post0
      - python-slugify==8.0.4
      - pytz==2025.2
      - requests==2.32.3
      - requests-mock==1.12.1
      - six==1.17.0
      - tabledata==1.3.4
      - tcolorpy==0.1.7
      - text-unidecode==1.3
      - tomli==2.2.1
      - typepy==1.3.4
      - typing-extensions==4.13.0
      - urllib3==2.3.0
prefix: /opt/conda/envs/pypistats

tests/test_pypistats.py::TestPypiStats::test__date_range tests/test_pypistats.py::TestPypiStats::test__date_range_no_dates_in_data
tests/test_pypistats.py::TestPypiStats::test__tabulate_html tests/test_pypistats.py::TestPypiStats::test__tabulate_markdown tests/test_pypistats.py::TestPypiStats::test__tabulate_noarg tests/test_pypistats.py::TestPypiStats::test__tabulate_rst tests/test_pypistats.py::TestPypiStats::test_overall_tabular_end_date tests/test_pypistats.py::TestPypiStats::test_overall_tabular_start_date tests/test_pypistats.py::TestPypiStats::test_recent_tabular tests/test_pypistats.py::TestPypiStats::test_system_tabular tests/test_pypistats.py::TestPypiStats::test_versions_are_strings tests/test_pypistats_cache.py::TestPypiStatsCache::test_subcommand_with_cache
tests/test_pypistats.py::TestPypiStats::test__filter_end_date tests/test_pypistats.py::TestPypiStats::test__filter_no_filters_no_change tests/test_pypistats.py::TestPypiStats::test__filter_start_and_end_date tests/test_pypistats.py::TestPypiStats::test__filter_start_date tests/test_pypistats.py::TestPypiStats::test__grand_total tests/test_pypistats.py::TestPypiStats::test__grand_total_one_row tests/test_pypistats.py::TestPypiStats::test__grand_total_recent tests/test_pypistats.py::TestPypiStats::test__monthly_total tests/test_pypistats.py::TestPypiStats::test__paramify_bool tests/test_pypistats.py::TestPypiStats::test__paramify_float tests/test_pypistats.py::TestPypiStats::test__paramify_int tests/test_pypistats.py::TestPypiStats::test__paramify_none tests/test_pypistats.py::TestPypiStats::test__paramify_string tests/test_pypistats.py::TestPypiStats::test__percent tests/test_pypistats.py::TestPypiStats::test__percent_one_row tests/test_pypistats.py::TestPypiStats::test__percent_recent tests/test_pypistats.py::TestPypiStats::test__sort tests/test_pypistats.py::TestPypiStats::test__sort_recent tests/test_pypistats.py::TestPypiStats::test__total tests/test_pypistats.py::TestPypiStats::test__total_recent tests/test_pypistats.py::TestPypiStats::test_python_major_json tests/test_pypistats.py::TestPypiStats::test_python_minor_json tests/test_pypistats.py::TestPypiStats::test_python_minor_monthly tests/test_pypistats.py::TestPypiStats::test_valid_json tests/test_pypistats.py::test__print_verbose_print tests/test_pypistats_cache.py::TestPypiStatsCache::test__cache_filename tests/test_pypistats_cache.py::TestPypiStatsCache::test__clear_cache tests/test_pypistats_cache.py::TestPypiStatsCache::test__load_cache_bad_data tests/test_pypistats_cache.py::TestPypiStatsCache::test__load_cache_not_exist tests/test_pypistats_cache.py::TestPypiStatsCache::test_cache_round_trip
src/pypistats/__init__.py tox.ini
src/pypistats/__init__.py tox.ini