Skip to content

Commit

Permalink
perf(web.utils): WebResponse: adopt propcache.cached_property
Browse files Browse the repository at this point in the history
Signed-off-by: Rongrong <[email protected]>
  • Loading branch information
Rongronggg9 committed Nov 4, 2024
1 parent ab60c0f commit 42ac7be
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 67 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dnspython[idna]==2.7.0

# utils
yarl==1.16.0
propcache==0.2.0
colorlog==6.8.2
APScheduler==3.10.4
python-dotenv==1.0.1
Expand Down
109 changes: 42 additions & 67 deletions src/web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
import email.utils
import feedparser
from contextlib import suppress
from dataclasses import dataclass, field
from dataclasses import dataclass
from datetime import datetime, timezone, timedelta
from ipaddress import ip_address, ip_network
from urllib.parse import urlparse
from multidict import CIMultiDictProxy
from propcache import cached_property

from .. import env, log
from ..i18n import i18n
Expand Down Expand Up @@ -136,99 +137,73 @@ def rfc_2822_8601_to_datetime(time_str: Optional[str]) -> Optional[datetime]:

@dataclass
class WebResponse:
AGE_REMAINING_CLAMP_MIN: ClassVar[int] = 0
AGE_REMAINING_CLAMP_MAX: ClassVar[int] = 3600 # 1 hour

url: str # redirected url
ori_url: str # original url
content: Optional[AnyStr]
headers: CIMultiDictProxy[str]
status: int
reason: Optional[str]

_now: Optional[datetime] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)
_date: Optional[datetime] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)
_last_modified: Optional[datetime] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)
_max_age: Optional[int] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)
_age: Optional[int] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)
_age_remaining: Optional[int] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)
_expires: Optional[datetime] = field(default=sentinel, init=False, repr=False, hash=False, compare=False)

_age_remaining_clamp: ClassVar[range] = range(0, 3600 + 1) # 1 hour

@property
@cached_property
def etag(self) -> Optional[str]:
return self.headers.get('ETag')
return self.headers.get('ETag') or None # Prohibit empty string

@property
@cached_property
def now(self) -> datetime:
if self._now is sentinel:
self._now = datetime.now(timezone.utc)
return self._now
return datetime.now(timezone.utc)

@now.setter
def now(self, value: datetime):
self._now = value

@property
@cached_property
def date(self) -> datetime:
if self._date is sentinel:
self._date = rfc_2822_8601_to_datetime(self.headers.get('Date')) or self.now
return self._date
return rfc_2822_8601_to_datetime(self.headers.get('Date')) or self.now

@property
@cached_property
def last_modified(self) -> datetime:
if self._last_modified is sentinel:
self._last_modified = rfc_2822_8601_to_datetime(self.headers.get('Last-Modified')) or self.date
return self._last_modified
return rfc_2822_8601_to_datetime(self.headers.get('Last-Modified')) or self.date

@property
@cached_property
def max_age(self) -> Optional[int]:
if self._max_age is not sentinel:
return self._max_age
cache_control = self.headers.get('Cache-Control', '').lower()
if not cache_control:
self._max_age = None
return None
elif 'no-cache' in cache_control or 'no-store' in cache_control:
self._max_age = 0
return 0
elif max_age := cache_control.partition('max-age=')[2].partition(',')[0]:
try:
self._max_age = int(max_age) if max_age else None
return int(max_age) if max_age else None
except ValueError:
self._max_age = None
else:
self._max_age = None
return self._max_age
return None
return None

@property
@cached_property
def age(self) -> Optional[int]:
if self._age is sentinel:
age = self.headers.get('Age')
try:
self._age = int(age) if age else None
except ValueError:
self._age = None
return self._age
age = self.headers.get('Age')
try:
return int(age) if age else None
except ValueError:
return None

@property
@cached_property
def age_remaining(self) -> Optional[int]:
if self._age_remaining is sentinel:
if self.max_age is None:
self._age_remaining = None
else:
self._age_remaining = self.max_age - (self.age or 0)
if self._age_remaining < self._age_remaining_clamp.start:
self._age_remaining = self._age_remaining_clamp.start
elif self._age_remaining > self._age_remaining_clamp.stop:
self._age_remaining = self._age_remaining_clamp.stop
return self._age_remaining

@property
if self.max_age is None:
return None
else:
age_remaining = self.max_age - (self.age or 0)
if age_remaining < (clamp_min := self.AGE_REMAINING_CLAMP_MIN):
return clamp_min
elif age_remaining > (clamp_max := self.AGE_REMAINING_CLAMP_MAX):
return clamp_max
return age_remaining

@cached_property
def expires(self) -> Optional[datetime]:
if self._expires is sentinel:
# max-age overrides Expires: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires
if self.age_remaining is None:
self._expires = rfc_2822_8601_to_datetime(self.headers.get('Expires'))
else:
self._expires = self.date + timedelta(seconds=self.age_remaining)
return self._expires
# max-age overrides Expires: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires
if self.age_remaining is None:
return rfc_2822_8601_to_datetime(self.headers.get('Expires'))
else:
return self.date + timedelta(seconds=self.age_remaining)


@dataclass
Expand Down

0 comments on commit 42ac7be

Please sign in to comment.