Skip to content
Merged
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
27 changes: 21 additions & 6 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@

## Summary

<!-- Here goes a general summary of what this release is about -->
This release removes the `timezonefinder` dependency to significantly reduce package size by 66M+ and enable ARM platform support.

> [!WARNING]
> This is a **breaking change** shipped in a patch release because this feature has no known users.

## Upgrading

<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
The `Location.timezone` field no longer performs automatic timezone lookup from latitude/longitude coordinates.

## New Features
```python
# Automatic timezone lookup (no longer works)
location = Location(latitude=52.52, longitude=13.405)
print(location.timezone) # Previously: ZoneInfo('Europe/Berlin'), now None
```

<!-- Here goes the main new features and examples or instructions on how to use them -->
If you need timezone lookup from coordinates, install [`timezonefinder`](https://pypi.org/project/timezonefinder/) separately and implement manual lookup:

## Bug Fixes
```python
# Install: pip install timezonefinder
from timezonefinder import TimezoneFinder
from zoneinfo import ZoneInfo
from frequenz.client.microgrid import Location

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
tf = TimezoneFinder()
tz_name = tf.timezone_at(lat=52.52, lng=13.405)
timezone = ZoneInfo(tz_name) if tz_name else None
location = Location(latitude=52.52, longitude=13.405, timezone=timezone)
```
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ dependencies = [
"frequenz-client-common >= 0.3.2, < 0.4.0",
"grpcio >= 1.63.0, < 2",
"protobuf >= 5.26.1, < 7",
"timezonefinder >= 6.2.0, < 7",
"typing-extensions >= 4.13.0, < 5",
]
dynamic = ["version"]
Expand Down
21 changes: 1 addition & 20 deletions src/frequenz/client/microgrid/_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
from zoneinfo import ZoneInfo

from frequenz.client.common.microgrid import MicrogridId
from timezonefinder import TimezoneFinder

_timezone_finder = TimezoneFinder()


@dataclass(frozen=True, kw_only=True)
Expand All @@ -23,23 +20,7 @@ class Location:
"""The longitude of the microgrid in degree."""

timezone: ZoneInfo | None = None
"""The timezone of the microgrid.

If not passed during construction (or `None` is passed), and there is a `longitude`
and `latitude`, then the timezone wil be looked up in a database based on the
coordinates. This lookup could fail, in which case the timezone will still be
`None`.
"""

def __post_init__(self) -> None:
"""Initialize the timezone of the microgrid."""
if self.latitude is None or self.longitude is None or self.timezone is not None:
return

timezone = _timezone_finder.timezone_at(lat=self.latitude, lng=self.longitude)
if timezone:
# The dataclass is frozen, so it needs to use __setattr__ to set the timezone.
object.__setattr__(self, "timezone", ZoneInfo(key=timezone))
"""The timezone of the microgrid."""


@dataclass(frozen=True, kw_only=True)
Expand Down
54 changes: 7 additions & 47 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

"""Tests for the microgrid metadata types."""

from collections.abc import Iterator
from unittest.mock import MagicMock, patch
from zoneinfo import ZoneInfo

import pytest
Expand All @@ -13,59 +11,20 @@
from frequenz.client.microgrid import Location, Metadata


@pytest.fixture
def timezone_finder() -> Iterator[MagicMock]:
"""Return a mock timezone finder."""
with patch(
"frequenz.client.microgrid._metadata._timezone_finder", autospec=True
) as mock_timezone_finder:
yield mock_timezone_finder


@pytest.mark.parametrize(
"latitude, longitude, timezone",
[
(None, None, None),
(52.52, None, None),
(None, 13.405, None),
(None, None, ZoneInfo(key="UTC")),
(52.52, None, ZoneInfo(key="UTC")),
(None, 13.405, ZoneInfo(key="UTC")),
(52.52, 13.405, ZoneInfo(key="UTC")),
],
ids=str,
)
def test_location_timezone_not_looked_up_if_not_possible_or_necessary(
timezone_finder: MagicMock,
@pytest.mark.parametrize("latitude", [None, 52.52], ids=str)
@pytest.mark.parametrize("longitude", [None, 13.405], ids=str)
@pytest.mark.parametrize("timezone", [None, ZoneInfo(key="UTC")], ids=str)
def test_location_initialization(
latitude: float | None,
longitude: float | None,
timezone: ZoneInfo | None,
) -> None:
"""Test the location timezone is not looked up if is not necessary or possible."""
timezone_finder.timezone_at.return_value = "Europe/Berlin"

"""Test location initialization with different combinations of parameters."""
location = Location(latitude=latitude, longitude=longitude, timezone=timezone)

assert location.latitude == latitude
assert location.longitude == longitude
assert location.timezone == timezone
timezone_finder.timezone_at.assert_not_called()


@pytest.mark.parametrize("timezone", [None, "Europe/Berlin"], ids=str)
def test_location_timezone_lookup(
timezone_finder: MagicMock, timezone: str | None
) -> None:
"""Test the location timezone is looked up if not provided and there is enough info."""
timezone_finder.timezone_at.return_value = timezone

location = Location(latitude=52.52, longitude=13.405)

if timezone is None:
assert location.timezone is None
else:
assert location.timezone == ZoneInfo(key=timezone)
timezone_finder.timezone_at.assert_called_once_with(lat=52.52, lng=13.405)


def test_metadata_initialization() -> None:
Expand All @@ -81,11 +40,12 @@ def test_metadata_initialization() -> None:
assert metadata.microgrid_id == microgrid_id
assert metadata.location is None

# Test with only location
# Test with only location - timezone should be None even with lat/lng
location = Location(latitude=52.52, longitude=13.405)
metadata = Metadata(location=location)
assert metadata.microgrid_id is None
assert metadata.location == location
assert metadata.location.timezone is None

# Test with both parameters
metadata = Metadata(microgrid_id=microgrid_id, location=location)
Expand Down
Loading