Skip to content

Commit 38c70ed

Browse files
committed
Remove dependency on pytz
All we are using pytz for is to using aware datetime object with UTC timezone, which is perfectly doable with the standard datetime and timezone objects. This commit also converts some instances of "native" datetimes to "aware". Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 908a0b3 commit 38c70ed

File tree

13 files changed

+218
-246
lines changed

13 files changed

+218
-246
lines changed

minimum-requirements-ci.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ pandas==1.3.5
99
protobuf==3.20.2
1010
pyarrow==6.0.0
1111
pydantic==1.9.0
12-
pytz==2021.3
1312
sympy==1.10.1
1413
toml==0.10
1514
tqdm==4.38.0

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ dependencies = [
3636
"protobuf >= 3.20, < 4",
3737
"pyarrow >= 6.0.0, < 6.1",
3838
"pydantic >= 1.9",
39-
"pytz >= 2021.3",
4039
"sympy >= 1.10.1, < 2",
4140
"toml >= 0.10",
4241
"tqdm >= 4.38.0, < 5",

src/frequenz/sdk/data_handling/time_series.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
"""
1010
from __future__ import annotations
1111

12-
import datetime
1312
import enum
1413
import logging
1514
from dataclasses import dataclass, field
15+
from datetime import datetime, timedelta, timezone
1616
from typing import Collection, Dict, Generic, Optional, Set, TypeVar
1717

18-
import pytz
19-
2018
from .formula import Formula
2119

2220
Key = TypeVar("Key")
@@ -104,13 +102,13 @@ class Status(enum.Enum):
104102
UNKNOWN = "unknown"
105103
ERROR = "error"
106104

107-
timestamp: datetime.datetime
105+
timestamp: datetime
108106
value: Optional[Value] = None
109107
status: Status = Status.VALID
110108
broken_component_ids: Set[int] = field(default_factory=set)
111109

112110
@staticmethod
113-
def create_error(timestamp: datetime.datetime) -> TimeSeriesEntry[Value]:
111+
def create_error(timestamp: datetime) -> TimeSeriesEntry[Value]:
114112
"""Create a `TimeSeriesEntry` that contains an error.
115113
116114
This can happen when the value would be NaN, e.g.
@@ -128,7 +126,7 @@ def create_error(timestamp: datetime.datetime) -> TimeSeriesEntry[Value]:
128126

129127
@staticmethod
130128
def create_unknown(
131-
timestamp: datetime.datetime, broken_component_ids: Optional[Set[int]] = None
129+
timestamp: datetime, broken_component_ids: Optional[Set[int]] = None
132130
) -> TimeSeriesEntry[Value]:
133131
"""Create a `TimeSeriesEntry` that contains an unknown value.
134132
@@ -174,11 +172,11 @@ class LatestEntryCache(Generic[Key, Value]):
174172

175173
def __init__(self) -> None:
176174
"""Initialize the class."""
177-
self._latest_timestamp = pytz.utc.localize(datetime.datetime.min)
175+
self._latest_timestamp = datetime.min.replace(tzinfo=timezone.utc)
178176
self._entries: Dict[Key, TimeSeriesEntry[Value]] = {}
179177

180178
@property
181-
def latest_timestamp(self) -> datetime.datetime:
179+
def latest_timestamp(self) -> datetime:
182180
"""Get the most recently observed timestamp across all keys in the cache.
183181
184182
Returns:
@@ -209,7 +207,7 @@ def __contains__(self, key: Key) -> bool:
209207
def get(
210208
self,
211209
key: Key,
212-
timedelta_tolerance: datetime.timedelta = datetime.timedelta.max,
210+
timedelta_tolerance: timedelta = timedelta.max,
213211
default: Optional[TimeSeriesEntry[Value]] = None,
214212
) -> CacheEntryLookupResult[Value]:
215213
"""Get the cached entry for the specified key, if any.
@@ -232,7 +230,7 @@ def get(
232230
retrieved from the cache has a timestamp greater than the latest saved
233231
timestamp across all cache keys.
234232
"""
235-
if timedelta_tolerance < datetime.timedelta(0):
233+
if timedelta_tolerance < timedelta(0):
236234
raise ValueError(
237235
f"timedelta_tolerance cannot be less than 0, but "
238236
f"{timedelta_tolerance} was provided"
@@ -320,7 +318,7 @@ def reset(self) -> None:
320318
slightly more efficient.
321319
"""
322320
self.clear()
323-
self._latest_timestamp = pytz.utc.localize(datetime.datetime.min)
321+
self._latest_timestamp = datetime.min.replace(tzinfo=timezone.utc)
324322

325323
def reset_latest_timestamp(self) -> bool:
326324
"""Reset the `latest_timestamp` property to the lowest possible value.
@@ -342,7 +340,7 @@ def reset_latest_timestamp(self) -> bool:
342340

343341
self._latest_timestamp = max(
344342
map(lambda x: x.timestamp, self._entries.values()),
345-
default=pytz.utc.localize(datetime.datetime.min),
343+
default=datetime.min.replace(tzinfo=timezone.utc),
346344
)
347345

348346
if self._latest_timestamp > previous:
@@ -423,7 +421,7 @@ def evaluate(
423421
cache: LatestEntryCache[str, Value],
424422
formula_name: str = "",
425423
symbol_to_symbol_mapping: Optional[Dict[str, SymbolMapping]] = None,
426-
timedelta_tolerance: datetime.timedelta = datetime.timedelta.max,
424+
timedelta_tolerance: timedelta = timedelta.max,
427425
default_entry: Optional[TimeSeriesEntry[Value]] = None,
428426
) -> Optional[TimeSeriesEntry[Value]]:
429427
"""Evaluate the formula using time-series values from the provided cache.
@@ -463,7 +461,7 @@ def evaluate(
463461
`timedelta_tolerance`
464462
"""
465463
kwargs: Dict[str, Optional[Value]] = {}
466-
timestamp = pytz.utc.localize(datetime.datetime.min)
464+
timestamp = datetime.min.replace(tzinfo=timezone.utc)
467465

468466
symbol_to_symbol_mapping = symbol_to_symbol_mapping or {}
469467
formula_broken_component_ids: Set[int] = set()

src/frequenz/sdk/data_ingestion/formula_calculator.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
License
88
MIT
99
"""
10-
import datetime as dt
1110
import logging
1211
from dataclasses import dataclass
12+
from datetime import datetime, timedelta, timezone
1313
from itertools import chain
1414
from typing import Any, Dict, List, Optional, Set, Tuple
1515

16-
import pytz
1716
import sympy
1817

1918
from ..data_handling.time_series import (
@@ -834,11 +833,9 @@ def compute(
834833
cache=self.symbol_values,
835834
formula_name=formula_name,
836835
symbol_to_symbol_mapping=self.symbol_mappings,
837-
timedelta_tolerance=dt.timedelta(
838-
seconds=self.component_data_timeout_sec
839-
),
836+
timedelta_tolerance=timedelta(seconds=self.component_data_timeout_sec),
840837
default_entry=TimeSeriesEntry[Any](
841-
timestamp=pytz.utc.localize(dt.datetime.now()), value=0.0
838+
timestamp=datetime.now(timezone.utc), value=0.0
842839
),
843840
)
844841
if res is not None:

src/frequenz/sdk/data_ingestion/load_historic_data.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@
1313
MIT
1414
"""
1515

16-
import datetime as dt
1716
import glob
1817
import itertools
1918
import logging
2019
import os
2120
from dataclasses import dataclass
21+
from datetime import datetime, timezone
2222
from typing import Any, Callable, List, Optional
2323

2424
import pandas as pd
2525
import pyarrow.parquet as pq
26-
import pytz
2726
from tqdm import tqdm
2827

2928
logger = logging.Logger(__name__)
@@ -151,8 +150,8 @@ def gen_date_dirs(data_dir: str, dates: pd.DatetimeIndex) -> List[str]:
151150

152151
def crop_df_list_by_time(
153152
df_list: List[pd.DataFrame],
154-
start_time: dt.datetime,
155-
end_time: dt.datetime,
153+
start_time: datetime,
154+
end_time: datetime,
156155
) -> pd.DataFrame:
157156
"""Concat and crop read data by the specified start and end time.
158157
@@ -169,7 +168,7 @@ def crop_df_list_by_time(
169168
specified start and end times.
170169
"""
171170
df0 = pd.concat(df_list).reset_index(drop=True)
172-
df0["ts"] = pd.to_datetime(df0["ts"]).dt.tz_localize(pytz.UTC)
171+
df0["ts"] = pd.to_datetime(df0["ts"]).tz_localize(timezone.utc)
173172
df0 = df0.loc[((df0["ts"] >= start_time) & (df0["ts"] <= end_time))].reset_index(
174173
drop=True
175174
)
@@ -256,15 +255,15 @@ def get_file_timestamps(self, filenames: List[str]) -> pd.Series:
256255
for file in filenames
257256
],
258257
format=self.file_time_format,
259-
).tz_localize(pytz.UTC)
258+
).tz_localize(timezone.utc)
260259
return timestamps
261260

262261
def gen_datafile_list(
263262
self,
264263
data_dir: str,
265264
dates: pd.DatetimeIndex,
266-
start_time: dt.datetime,
267-
end_time: dt.datetime,
265+
start_time: datetime,
266+
end_time: datetime,
268267
) -> List[str]:
269268
"""Generate the list of historic parquet files to read.
270269
@@ -352,8 +351,8 @@ def load_parquet_files(
352351
def read(
353352
self,
354353
load_hd_settings: LoadHistoricDataSettings,
355-
start_time: dt.datetime,
356-
end_time: dt.datetime,
354+
start_time: datetime,
355+
end_time: datetime,
357356
) -> pd.DataFrame:
358357
"""Read historical data.
359358
@@ -373,9 +372,9 @@ def read(
373372
"component_id=" + str(load_hd_settings.component_info.component_id),
374373
)
375374
if start_time.tzinfo is None:
376-
start_time = start_time.replace(tzinfo=pytz.UTC)
375+
start_time = start_time.replace(tzinfo=timezone.utc)
377376
if end_time.tzinfo is None:
378-
end_time = end_time.replace(tzinfo=pytz.UTC)
377+
end_time = end_time.replace(tzinfo=timezone.utc)
379378
logger.info(
380379
"reading historic data from component id=%s within the time interval: %s to %s",
381380
load_hd_settings.component_info.component_id,

src/frequenz/sdk/data_ingestion/microgrid_data.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
MIT
1212
"""
1313
import asyncio
14-
import datetime as dt
1514
import logging
15+
from datetime import datetime, timezone
1616
from typing import Any, Dict, List, Optional, Set
1717

18-
import pytz
1918
from frequenz.channels import Broadcast, Merge, Receiver, Select, Sender
2019

2120
from ..actor.decorator import actor
@@ -99,7 +98,7 @@ async def resend_formulas(self) -> None:
9998
await asyncio.sleep(self._wait_for_data_sec)
10099
tasks: List["asyncio.Task[bool]"] = []
101100
while True:
102-
start_time = dt.datetime.now(tz=pytz.UTC)
101+
start_time = datetime.now(timezone.utc)
103102
# For every formula that was updated at least once, send that formula.
104103
for name, formula_result in self.formula_calculator.results.items():
105104
if name not in self._outputs:
@@ -114,7 +113,7 @@ async def resend_formulas(self) -> None:
114113
tasks.append(task)
115114

116115
await asyncio.gather(*tasks, return_exceptions=True)
117-
diff: float = (dt.datetime.now(pytz.UTC) - start_time).total_seconds()
116+
diff: float = (datetime.now(timezone.utc) - start_time).total_seconds()
118117
if diff >= self._formula_update_interval_sec:
119118
logger.error(
120119
"Sending results of formulas took too long: %f, "

src/frequenz/sdk/power_distribution/utils.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
MIT
88
"""
99
from dataclasses import dataclass
10-
from datetime import datetime
10+
from datetime import datetime, timezone
1111
from enum import Enum
1212
from typing import Dict, NamedTuple, Optional, Set
1313

@@ -100,7 +100,7 @@ def mark_as_broken(self, component_id: int) -> None:
100100
Args:
101101
component_id: component id
102102
"""
103-
self._broken[component_id] = datetime.now()
103+
self._broken[component_id] = datetime.now(timezone.utc)
104104

105105
def update_retry(self, timeout_sec: float) -> None:
106106
"""Change how long the component should be marked as broken.
@@ -121,7 +121,9 @@ def is_broken(self, component_id: int) -> bool:
121121
"""
122122
if component_id in self._broken:
123123
last_broken = self._broken[component_id]
124-
if (datetime.now() - last_broken).total_seconds() < self._timeout_sec:
124+
if (
125+
datetime.now(timezone.utc) - last_broken
126+
).total_seconds() < self._timeout_sec:
125127
return True
126128

127129
del self._broken[component_id]

tests/power_distribution/test_distribution_algorithm.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
MIT
99
"""
1010
from dataclasses import dataclass
11-
from datetime import datetime
11+
from datetime import datetime, timezone
1212
from typing import Dict, List, Optional
1313

1414
import frequenz.api.microgrid.microgrid_pb2 as microgrid_pb
@@ -72,7 +72,7 @@ def create_battery_msg( # pylint: disable=too-many-arguments
7272
capacity: Metric,
7373
soc: Metric,
7474
power: Bound,
75-
timestamp: datetime = datetime.utcnow(),
75+
timestamp: datetime = datetime.now(timezone.utc),
7676
) -> microgrid_pb.ComponentData:
7777
"""Create protobuf battery components with given arguments.
7878
@@ -111,7 +111,7 @@ def create_battery_msg( # pylint: disable=too-many-arguments
111111
def create_inverter_msg(
112112
component_id: int,
113113
power: Bound,
114-
timestamp: datetime = datetime.utcnow(),
114+
timestamp: datetime = datetime.now(timezone.utc),
115115
) -> microgrid_pb.ComponentData:
116116
"""Create protobuf inverter components with given arguments.
117117

tests/power_distribution/test_power_distributor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import asyncio
1010
import re
1111
from dataclasses import dataclass
12-
from datetime import datetime, timedelta
12+
from datetime import datetime, timedelta, timezone
1313
from functools import partial
1414
from typing import Dict, Set, Tuple, TypeVar, Union
1515
from unittest import IsolatedAsyncioTestCase, mock
@@ -605,7 +605,7 @@ async def test_power_distributor_stale_battery_message(
605605
capacity=Metric(98000),
606606
soc=Metric(40, Bound(20, 80)),
607607
power=Bound(-1000, 1000),
608-
timestamp=datetime.utcnow() - timedelta(seconds=62),
608+
timestamp=datetime.now(timezone.utc) - timedelta(seconds=62),
609609
)
610610
else:
611611
bat = create_battery_msg(
@@ -672,7 +672,7 @@ async def test_power_distributor_stale_all_components_message(
672672
capacity=Metric(98000),
673673
soc=Metric(40, Bound(20, 80)),
674674
power=Bound(-1000, 1000),
675-
timestamp=datetime.utcnow() - timedelta(seconds=62),
675+
timestamp=datetime.now(timezone.utc) - timedelta(seconds=62),
676676
)
677677
else:
678678
bat = create_battery_msg(
@@ -689,7 +689,7 @@ async def test_power_distributor_stale_all_components_message(
689689
inv = create_inverter_msg(
690690
key_id,
691691
power=Bound(-500, 500),
692-
timestamp=datetime.utcnow() - timedelta(seconds=62),
692+
timestamp=datetime.now(timezone.utc) - timedelta(seconds=62),
693693
)
694694
else:
695695
inv = create_inverter_msg(

0 commit comments

Comments
 (0)