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
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ switch, and thus all their contributions are dual-licensed.
- Brandon W Maister <quodlibetor@MASKED>
- Brock Mendel <jbrockmendel@MASKED> (gh: @jbrockmendel) **R**
- Carlos <carlosxl@MASKED>
- Christopher Cordero <ccordero@pm.me> (gh: cs-cordero) **D**
- Christopher Corley <cscorley@MASKED>
- Claudio Canepa <ccanepacc@MASKED>
- Daniel Lepage <dplepage@MASKED>
Expand Down
1 change: 1 addition & 0 deletions changelog.d/672.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Switched the ``tzoffset``, ``tzstr`` and ``gettz`` caches over to using weak references, so that the cache expires when no other references to the original ``tzinfo`` objects exist. This cache-expiry behavior is not considered part of the public interface and may change in the future. Requested by @pganssle (gh issue #635), implemented by @cs-cordero (gh pr #672).
49 changes: 49 additions & 0 deletions dateutil/test/test_tz.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import sys
import base64
import copy
import gc
import weakref

from functools import partial

Expand Down Expand Up @@ -731,6 +733,20 @@ def testTzOffsetSingletonDifferent(self):

assert tz1 is tz2


@pytest.mark.smoke
@pytest.mark.tzoffset
def test_tzoffset_weakref():
UTC1 = tz.tzoffset('UTC', 0)
UTC_ref = weakref.ref(tz.tzoffset('UTC', 0))
UTC1 is UTC_ref()
del UTC1
gc.collect()

assert UTC_ref() is None
assert UTC_ref() is not tz.tzoffset('UTC', 0)


@pytest.mark.tzoffset
@pytest.mark.parametrize('args', [
('UTC', 0),
Expand Down Expand Up @@ -1037,6 +1053,7 @@ def testGettzCacheTzLocal(self):

assert local1 is not local2


@pytest.mark.gettz
@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
def test_gettz_cache_clear():
Expand All @@ -1048,6 +1065,23 @@ def test_gettz_cache_clear():
assert NYC1 is not NYC2


@pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo")
@pytest.mark.smoke
@pytest.mark.gettz
def test_gettz_weakref():
tz.gettz.cache_clear()
NYC1 = tz.gettz('America/New_York')
NYC_ref = weakref.ref(tz.gettz('America/New_York'))

assert NYC1 is NYC_ref()

del NYC1
gc.collect()

assert NYC_ref() is None
assert tz.gettz('America/New_York') is not NYC_ref()


class ZoneInfoGettzTest(GettzTest, WarningTestMixin):
def gettz(self, name):
zoneinfo_file = zoneinfo.get_zonefile_instance()
Expand Down Expand Up @@ -1375,6 +1409,21 @@ def testTzStrInstance(self):
# Ensure that these still are all the same zone
assert tz1 == tz2 == tz3


@pytest.mark.smoke
@pytest.mark.tzstr
def test_tzstr_weakref():
tz_t1 = tz.tzstr('EST5EDT')
tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT'))
assert tz_t1 is tz_t2_ref()

del tz_t1
gc.collect()

assert tz_t2_ref() is None
assert tz.tzstr('EST5EDT') is not tz_t2_ref()


@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str,expected', [
# From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
Expand Down
6 changes: 3 additions & 3 deletions dateutil/tz/_factories.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import timedelta

import weakref

class _TzSingleton(type):
def __init__(cls, *args, **kwargs):
Expand All @@ -19,7 +19,7 @@ def instance(cls, *args, **kwargs):

class _TzOffsetFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
cls.__instances = weakref.WeakValueDictionary()

def __call__(cls, name, offset):
if isinstance(offset, timedelta):
Expand All @@ -36,7 +36,7 @@ def __call__(cls, name, offset):

class _TzStrFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
cls.__instances = weakref.WeakValueDictionary()

def __call__(cls, s, posix_offset=False):
key = (s, posix_offset)
Expand Down
5 changes: 3 additions & 2 deletions dateutil/tz/tz.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import sys
import os
import bisect
import weakref

import six
from six import string_types
Expand Down Expand Up @@ -1406,7 +1407,7 @@ def __get_gettz():
class GettzFunc(object):
def __init__(self):

self.__instances = {}
self.__instances = weakref.WeakValueDictionary()
self._cache_lock = _thread.allocate_lock()

def __call__(self, name=None):
Expand All @@ -1425,7 +1426,7 @@ def __call__(self, name=None):

def cache_clear(self):
with self._cache_lock:
self.__instances = {}
self.__instances = weakref.WeakValueDictionary()

@staticmethod
def nocache(name=None):
Expand Down