Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ History
``aiohttp.encode_basic_auth()`` instead of the ``aiohttp.BasicAuth`` /
``auth=`` parameter, which are deprecated as of aiohttp 3.14.0. As a result,
the minimum required ``aiohttp`` version is now 3.14.0.
* ``pygeoipupdate`` now identifies the database to download by its build
epoch rather than its date. Previously, if a new build was published on the
same day during an update, the download could fail with a checksum mismatch.

0.1.0 (2026-02-10)
++++++++++++++++++
Expand Down
13 changes: 5 additions & 8 deletions src/pygeoipupdate/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ async def get_metadata(self, edition_id: str) -> Metadata:
db = databases[0]
return Metadata(
edition_id=db["edition_id"],
date=db["date"],
build_epoch=db["build_epoch"],
md5=db["md5"],
)
except (KeyError, IndexError) as e:
Expand Down Expand Up @@ -203,7 +203,7 @@ async def download(

# Download the database
compressed_path, last_modified = await self._fetch_database(
edition_id, metadata.date, temp_dir
edition_id, metadata.build_epoch, temp_dir
)

return UpdateAvailable(
Expand All @@ -215,14 +215,14 @@ async def download(
async def _fetch_database(
self,
edition_id: str,
date: str,
build_epoch: int,
temp_dir: Path,
) -> tuple[Path, datetime | None]:
"""Fetch the database file, streaming to a temp file.

Args:
edition_id: The database edition ID.
date: The database date in YYYY-MM-DD format.
build_epoch: The build to download, from the metadata response.
temp_dir: Directory in which to create the download temp file.

Returns:
Expand All @@ -234,10 +234,7 @@ async def _fetch_database(
msg = "Client must be used as async context manager"
raise RuntimeError(msg)

# Convert date format from YYYY-MM-DD to YYYYMMDD
date_param = date.replace("-", "")

params = urlencode({"date": date_param, "suffix": "tar.gz"})
params = urlencode({"build_epoch": build_epoch, "suffix": "tar.gz"})
escaped_edition = quote(edition_id, safe="")
url = f"{self._host}/geoip/databases/{escaped_edition}/download?{params}"

Expand Down
4 changes: 2 additions & 2 deletions src/pygeoipupdate/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ class Metadata:

Attributes:
edition_id: The database edition ID.
date: The database date in YYYY-MM-DD format.
build_epoch: Identifies the exact build that md5 describes.
md5: The MD5 hash of the database file.

"""

edition_id: str
date: str
build_epoch: int
md5: str


Expand Down
4 changes: 4 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def test_successful_update(self, httpserver: HTTPServer, tmp_path: Path) -> None
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -137,6 +138,7 @@ def test_verbose_output(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -177,6 +179,7 @@ def test_json_output(self, httpserver: HTTPServer, tmp_path: Path) -> None:
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -262,6 +265,7 @@ def test_env_var_config(self, httpserver: HTTPServer, tmp_path: Path) -> None:
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down
11 changes: 9 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ async def test_get_metadata(self, httpserver: HTTPServer) -> None:
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "abc123def456",
}
]
Expand All @@ -44,7 +45,7 @@ async def test_get_metadata(self, httpserver: HTTPServer) -> None:
metadata = await client.get_metadata("GeoLite2-City")

assert metadata.edition_id == "GeoLite2-City"
assert metadata.date == "2024-01-15"
assert metadata.build_epoch == 1705320000
assert metadata.md5 == "abc123def456"

@pytest.mark.asyncio
Expand All @@ -58,6 +59,7 @@ async def test_sends_authorization_header(self, httpserver: HTTPServer) -> None:
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "abc123def456",
}
]
Expand Down Expand Up @@ -138,6 +140,7 @@ async def test_download_no_update_needed(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "current_hash",
}
]
Expand Down Expand Up @@ -169,6 +172,7 @@ async def test_download_with_update(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "new_hash",
}
]
Expand All @@ -177,7 +181,7 @@ async def test_download_with_update(

httpserver.expect_request(
"/geoip/databases/GeoLite2-City/download",
query_string="date=20240115&suffix=tar.gz",
query_string="build_epoch=1705320000&suffix=tar.gz",
).respond_with_data(
tar_gz_data,
content_type="application/gzip",
Expand Down Expand Up @@ -208,6 +212,7 @@ async def test_download_auth_error(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "new_hash",
}
]
Expand Down Expand Up @@ -244,6 +249,7 @@ async def test_download_malformed_last_modified(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "new_hash",
}
]
Expand Down Expand Up @@ -314,6 +320,7 @@ async def test_download_http_500_on_fetch(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": "new_hash",
}
]
Expand Down
28 changes: 25 additions & 3 deletions tests/test_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async def test_download_new_database(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -104,6 +105,7 @@ async def test_no_update_needed(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -158,6 +160,7 @@ async def test_multiple_editions(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": city_hash,
}
]
Expand All @@ -177,6 +180,7 @@ async def test_multiple_editions(
{
"edition_id": "GeoLite2-Country",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": country_hash,
}
]
Expand Down Expand Up @@ -229,7 +233,12 @@ async def test_parallel_downloads(
).respond_with_json(
{
"databases": [
{"edition_id": edition, "date": "2024-01-15", "md5": md5_hash}
{
"edition_id": edition,
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": md5_hash,
}
]
}
)
Expand Down Expand Up @@ -276,7 +285,12 @@ async def test_parallel_error_cancels_siblings(
).respond_with_json(
{
"databases": [
{"edition_id": "Edition1", "date": "2024-01-15", "md5": e1_hash}
{
"edition_id": "Edition1",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": e1_hash,
}
]
}
)
Expand Down Expand Up @@ -308,7 +322,12 @@ async def test_parallel_error_cancels_siblings(
).respond_with_json(
{
"databases": [
{"edition_id": "Edition3", "date": "2024-01-15", "md5": e3_hash}
{
"edition_id": "Edition3",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": e3_hash,
}
]
}
)
Expand Down Expand Up @@ -361,6 +380,7 @@ async def test_file_locking(self, httpserver: HTTPServer, tmp_path: Path) -> Non
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -428,6 +448,7 @@ async def test_download_without_retry(
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down Expand Up @@ -483,6 +504,7 @@ def metadata_handler(_request: Request) -> Response:
{
"edition_id": "GeoLite2-City",
"date": "2024-01-15",
"build_epoch": 1705320000,
"md5": mmdb_hash,
}
]
Expand Down
Loading