Skip to content

Commit

Permalink
Merge pull request frappe#28093 from gavindsouza/perf-datetime-utils
Browse files Browse the repository at this point in the history
refactor: Replace pytz to std lib zoneinfo & datetime
  • Loading branch information
akhilnarang authored Dec 6, 2024
2 parents fd1357c + 94fe90d commit 14510ea
Show file tree
Hide file tree
Showing 13 changed files with 49 additions and 52 deletions.
4 changes: 2 additions & 2 deletions frappe/core/doctype/rq_worker/rq_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import datetime
from contextlib import suppress

import pytz
from rq import Worker

import frappe
Expand Down Expand Up @@ -104,6 +103,7 @@ def serialize_worker(worker: Worker) -> frappe._dict:
def compute_utilization(worker: Worker) -> float:
with suppress(Exception):
total_time = (
datetime.datetime.now(pytz.UTC) - worker.birth_date.replace(tzinfo=pytz.UTC)
datetime.datetime.now(datetime.timezone.utc)
- worker.birth_date.replace(tzinfo=datetime.timezone.utc)
).total_seconds()
return worker.total_working_time / total_time * 100
4 changes: 2 additions & 2 deletions frappe/core/doctype/user/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,9 +808,9 @@ def check_roles_added(self):

@frappe.whitelist()
def get_timezones():
import pytz
import zoneinfo

return {"timezones": pytz.all_timezones}
return {"timezones": zoneinfo.available_timezones()}


@frappe.whitelist()
Expand Down
10 changes: 7 additions & 3 deletions frappe/deprecation_dumpster.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,15 +814,19 @@ def switch_site(self, site: str):

@contextmanager
def freeze_time(self, time_to_freeze, is_utc=False, *args, **kwargs):
import pytz
from zoneinfo import ZoneInfo

from freezegun import freeze_time

from frappe.utils.data import convert_utc_to_timezone, get_datetime, get_system_timezone

if not is_utc:
# Freeze time expects UTC or tzaware objects. We have neither, so convert to UTC.
timezone = pytz.timezone(get_system_timezone())
time_to_freeze = timezone.localize(get_datetime(time_to_freeze)).astimezone(pytz.utc)
time_to_freeze = (
get_datetime(time_to_freeze)
.replace(tzinfo=ZoneInfo(get_system_timezone()))
.astimezone(ZoneInfo("UTC"))
)

with freeze_time(time_to_freeze, *args, **kwargs):
yield
Expand Down
8 changes: 3 additions & 5 deletions frappe/email/frappemail.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from datetime import datetime
from typing import Any
from urllib.parse import urljoin

import pytz
from zoneinfo import ZoneInfo

import frappe
from frappe import _
Expand Down Expand Up @@ -122,12 +121,11 @@ def pull_raw(self, limit: int = 50, last_synced_at: str | None = None) -> dict[s

def add_or_update_tzinfo(date_time: datetime | str, timezone: str | None = None) -> str:
"""Adds or updates timezone to the datetime."""

date_time = get_datetime(date_time)
target_tz = pytz.timezone(timezone or get_system_timezone())
target_tz = ZoneInfo(timezone or get_system_timezone())

if date_time.tzinfo is None:
date_time = target_tz.localize(date_time)
date_time = date_time.replace(tzinfo=target_tz)
else:
date_time = date_time.astimezone(target_tz)

Expand Down
14 changes: 6 additions & 8 deletions frappe/integrations/doctype/token_cache/token_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
# License: MIT. See LICENSE

import datetime

import pytz
from zoneinfo import ZoneInfo

import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, cstr, get_system_timezone
from frappe.utils import cint, cstr, get_datetime, get_system_timezone


class TokenCache(Document):
Expand Down Expand Up @@ -73,11 +72,10 @@ def update_data(self, data):
return self

def get_expires_in(self):
system_timezone = pytz.timezone(get_system_timezone())
modified = frappe.utils.get_datetime(self.modified)
modified = system_timezone.localize(modified)
expiry_utc = modified.astimezone(pytz.utc) + datetime.timedelta(seconds=self.expires_in)
now_utc = datetime.datetime.now(pytz.utc)
system_timezone = ZoneInfo(get_system_timezone())
modified: datetime.datetime = get_datetime(self.modified).replace(tzinfo=system_timezone)
expiry_utc = modified.astimezone(datetime.timezone.utc) + datetime.timedelta(seconds=self.expires_in)
now_utc = datetime.datetime.now(datetime.timezone.utc)
return cint((expiry_utc - now_utc).total_seconds())

def is_expired(self):
Expand Down
7 changes: 3 additions & 4 deletions frappe/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import traceback
import uuid

import pytz
import rq

import frappe
Expand Down Expand Up @@ -52,7 +51,7 @@ def __init__(self, transaction_type, method, kwargs):
self.data = frappe._dict(
{
"site": frappe.local.site,
"timestamp": datetime.datetime.now(pytz.UTC),
"timestamp": datetime.datetime.now(datetime.timezone.utc),
"transaction_type": transaction_type,
"uuid": str(uuid.uuid4()),
}
Expand Down Expand Up @@ -85,7 +84,7 @@ def collect_job_meta(self, method, kwargs):

if job := rq.get_current_job():
self.data.uuid = job.id
waitdiff = self.data.timestamp - job.enqueued_at.replace(tzinfo=pytz.UTC)
waitdiff = self.data.timestamp - job.enqueued_at.replace(tzinfo=datetime.timezone.utc)
self.data.job.wait = int(waitdiff.total_seconds() * 1000000)

def add_custom_data(self, **kwargs):
Expand All @@ -94,7 +93,7 @@ def add_custom_data(self, **kwargs):

def dump(self, response=None):
try:
timediff = datetime.datetime.now(pytz.UTC) - self.data.timestamp
timediff = datetime.datetime.now(datetime.timezone.utc) - self.data.timestamp
# Obtain duration in microseconds
self.data.duration = int(timediff.total_seconds() * 1000000)

Expand Down
1 change: 0 additions & 1 deletion frappe/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from urllib.parse import unquote, urljoin, urlparse

import jwt
import pytz
from oauthlib.openid import RequestValidator

import frappe
Expand Down
5 changes: 2 additions & 3 deletions frappe/rate_limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from collections.abc import Callable
from functools import wraps

import pytz
from werkzeug.wrappers import Response

import frappe
Expand Down Expand Up @@ -35,7 +34,7 @@ def __init__(self, limit, window):
self.limit = int(limit * 1000000)
self.window = window

self.start = datetime.datetime.now(pytz.UTC)
self.start = datetime.datetime.now(datetime.timezone.utc)
timestamp = int(frappe.utils.now_datetime().timestamp())

self.window_number, self.spent = divmod(timestamp, self.window)
Expand Down Expand Up @@ -80,7 +79,7 @@ def headers(self):
def record_request_end(self):
if self.end is not None:
return
self.end = datetime.datetime.now(pytz.UTC)
self.end = datetime.datetime.now(datetime.timezone.utc)
self.duration = int((self.end - self.start).total_seconds() * 1000000)

def respond(self):
Expand Down
10 changes: 6 additions & 4 deletions frappe/tests/classes/context_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from collections.abc import Callable
from contextlib import contextmanager
from functools import wraps
from inspect import isfunction, ismethod
from typing import TYPE_CHECKING, Any

import frappe
Expand All @@ -28,15 +27,18 @@
@contextmanager
def freeze_time(time_to_freeze: Any, is_utc: bool = False, *args: Any, **kwargs: Any) -> None:
"""Temporarily: freeze time with freezegun."""
import pytz
from datetime import UTC
from zoneinfo import ZoneInfo

from freezegun import freeze_time as freezegun_freeze_time

from frappe.utils.data import get_datetime, get_system_timezone

if not is_utc:
# Freeze time expects UTC or tzaware objects. We have neither, so convert to UTC.
timezone = pytz.timezone(get_system_timezone())
time_to_freeze = timezone.localize(get_datetime(time_to_freeze)).astimezone(pytz.utc)
time_to_freeze = (
get_datetime(time_to_freeze).replace(tzinfo=ZoneInfo(get_system_timezone())).astimezone(UTC)
)

with freezegun_freeze_time(time_to_freeze, *args, **kwargs):
yield
Expand Down
7 changes: 3 additions & 4 deletions frappe/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
import json
import os
import sys
from datetime import date, datetime, time, timedelta
from datetime import date, datetime, time, timedelta, timezone
from decimal import ROUND_HALF_UP, Decimal, localcontext
from enum import Enum
from io import StringIO
from mimetypes import guess_type
from unittest.mock import patch

import pytz
from hypothesis import given
from hypothesis import strategies as st
from PIL import Image
Expand Down Expand Up @@ -736,9 +735,9 @@ class TEST(Enum):
minute=23,
second=23,
microsecond=23,
tzinfo=pytz.utc,
tzinfo=timezone.utc,
),
time(hour=23, minute=23, second=23, microsecond=23, tzinfo=pytz.utc),
time(hour=23, minute=23, second=23, microsecond=23, tzinfo=timezone.utc),
timedelta(days=10, hours=12, minutes=120, seconds=10),
],
"float": [
Expand Down
12 changes: 7 additions & 5 deletions frappe/utils/caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
from collections.abc import Callable
from functools import wraps

import pytz

import frappe

_SITE_CACHE = defaultdict(lambda: defaultdict(dict))
Expand Down Expand Up @@ -115,7 +113,9 @@ def clear_cache():

if ttl is not None and not callable(ttl):
func.ttl = ttl
func.expiration = datetime.datetime.now(pytz.UTC) + datetime.timedelta(seconds=func.ttl)
func.expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
seconds=func.ttl
)

if maxsize is not None and not callable(maxsize):
func.maxsize = maxsize
Expand All @@ -125,9 +125,11 @@ def site_cache_wrapper(*args, **kwargs):
if getattr(frappe.local, "initialised", None):
func_call_key = json.dumps((args, kwargs))

if hasattr(func, "ttl") and datetime.datetime.now(pytz.UTC) >= func.expiration:
if hasattr(func, "ttl") and datetime.datetime.now(datetime.timezone.utc) >= func.expiration:
func.clear_cache()
func.expiration = datetime.datetime.now(pytz.UTC) + datetime.timedelta(seconds=func.ttl)
func.expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
seconds=func.ttl
)

if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][frappe.local.site]) >= func.maxsize:
_SITE_CACHE[func_key][frappe.local.site].pop(
Expand Down
16 changes: 7 additions & 9 deletions frappe/utils/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from enum import Enum
from typing import Any, Literal, Optional, TypeVar
from urllib.parse import parse_qsl, quote, urlencode, urljoin, urlparse, urlunparse
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

import pytz
from click import secho
from dateutil import parser
from dateutil.parser import ParserError
Expand Down Expand Up @@ -350,8 +350,7 @@ def time_diff_in_hours(string_ed_date: DateTimeLikeObject, string_st_date: DateT

def now_datetime() -> datetime.datetime:
"""Return the current datetime in system timezone."""
dt = convert_utc_to_system_timezone(datetime.datetime.now(pytz.UTC))
return dt.replace(tzinfo=None)
return datetime.datetime.now(ZoneInfo(get_system_timezone())).replace(tzinfo=None)


def get_timestamp(date: Optional["DateTimeLikeObject"] = None) -> float:
Expand All @@ -372,19 +371,18 @@ def get_system_timezone() -> str:


def convert_utc_to_timezone(utc_timestamp: datetime.datetime, time_zone: str) -> datetime.datetime:
from pytz import UnknownTimeZoneError, timezone

if utc_timestamp.tzinfo is None:
utc_timestamp = timezone("UTC").localize(utc_timestamp)
utc_timestamp = utc_timestamp.replace(tzinfo=ZoneInfo(time_zone))

try:
return utc_timestamp.astimezone(timezone(time_zone))
except UnknownTimeZoneError:
return utc_timestamp.astimezone(ZoneInfo(time_zone))
except ZoneInfoNotFoundError:
return utc_timestamp


def get_datetime_in_timezone(time_zone: str) -> datetime.datetime:
"""Return the current datetime in the given timezone (e.g. 'Asia/Kolkata')."""
utc_timestamp = datetime.datetime.now(pytz.UTC)
utc_timestamp = datetime.datetime.now(datetime.timezone.utc)
return convert_utc_to_timezone(utc_timestamp, time_zone)


Expand Down
3 changes: 1 addition & 2 deletions frappe/utils/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import time
from typing import NoReturn

import pytz
import setproctitle
from croniter import CroniterBadCronError
from filelock import FileLock, Timeout
Expand Down Expand Up @@ -72,7 +71,7 @@ def sleep_duration(tick):
# This makes scheduler aligned with real clock,
# so event scheduled at 12:00 happen at 12:00 and not 12:00:35.
minutes = tick // 60
now = datetime.datetime.now(pytz.UTC)
now = datetime.datetime.now(datetime.timezone.utc)
left_minutes = minutes - now.minute % minutes
next_execution = now.replace(second=0) + datetime.timedelta(minutes=left_minutes)

Expand Down

0 comments on commit 14510ea

Please sign in to comment.