Drop frozendict hard dependency in favour of an internal fallback

This commit is contained in:
Alessandro Colace
2026-05-17 23:09:29 +02:00
parent 46ce563111
commit 593b47c36d
5 changed files with 83 additions and 4 deletions
-1
View File
@@ -4,7 +4,6 @@ requests>=2.31
multitasking>=0.0.7
platformdirs>=2.0.0
pytz>=2022.5
frozendict>=2.3.4
beautifulsoup4>=4.11.1
lxml>=4.9.0
peewee>=3.16.2
+1 -1
View File
@@ -62,7 +62,7 @@ setup(
install_requires=['pandas>=1.3.0', 'numpy>=1.16.5',
'requests>=2.31', 'multitasking>=0.0.7',
'platformdirs>=2.0.0', 'pytz>=2022.5',
'frozendict>=2.3.4', 'peewee>=3.16.2',
'peewee>=3.16.2',
'beautifulsoup4>=4.11.1', 'curl_cffi>=0.15',
'protobuf>=3.19.0', 'websockets>=13.0'],
extras_require={
+53
View File
@@ -0,0 +1,53 @@
"""Tests for yfinance.data internal helpers."""
import unittest
from functools import lru_cache
from yfinance.data import lru_cache_freezeargs
from yfinance.utils import frozendict
class TestFrozenDict(unittest.TestCase):
def test_is_dict_subclass(self):
d = frozendict({"a": 1, "b": 2})
self.assertEqual(d["a"], 1)
self.assertEqual(d.get("b"), 2)
self.assertEqual(set(d.keys()), {"a", "b"})
self.assertIsInstance(d, dict)
def test_is_hashable(self):
d1 = frozendict({"a": 1, "b": 2})
d2 = frozendict({"b": 2, "a": 1})
self.assertEqual(hash(d1), hash(d2))
self.assertEqual(len({d1, d2}), 1)
def test_mutation_raises(self):
d = frozendict({"a": 1})
with self.assertRaises(TypeError):
d["b"] = 2
with self.assertRaises(TypeError):
del d["a"]
with self.assertRaises(AttributeError):
d.pop("a")
with self.assertRaises(AttributeError):
d.clear()
with self.assertRaises(AttributeError):
d.update({"x": 9})
def test_lru_cache_integration(self):
call_count = {"n": 0}
@lru_cache_freezeargs
@lru_cache(maxsize=8)
def fn(params):
call_count["n"] += 1
return sum(params.values())
self.assertEqual(fn({"a": 1, "b": 2}), 3)
self.assertEqual(fn({"a": 1, "b": 2}), 3)
self.assertEqual(call_count["n"], 1)
self.assertEqual(fn({"a": 1, "b": 3}), 4)
self.assertEqual(call_count["n"], 2)
if __name__ == "__main__":
unittest.main()
+1 -2
View File
@@ -9,9 +9,8 @@ from urllib.parse import urlsplit, urljoin
from bs4 import BeautifulSoup
import datetime
from frozendict import frozendict
from . import utils, cache
from .utils import frozendict
from .config import YfConfig
import threading
+28
View File
@@ -43,6 +43,34 @@ from yfinance import const
from yfinance.exceptions import YFException
from yfinance.config import YfConfig
# Use the third-party ``frozendict`` package if installed; otherwise fall
# back to a small pure-Python equivalent (PEP 814).
try:
from frozendict import frozendict # type: ignore[import-not-found]
except ImportError:
class frozendict(dict): # type: ignore[no-redef]
"""Hashable, read-only ``dict`` used as an ``lru_cache`` key."""
__slots__ = ()
def __hash__(self): # type: ignore[override]
return hash(frozenset(self.items()))
def __setitem__(self, *args, **kwargs):
raise TypeError(f"'{type(self).__name__}' object doesn't support item assignment")
def __delitem__(self, *args, **kwargs):
raise TypeError(f"'{type(self).__name__}' object doesn't support item deletion")
def _readonly(self, *args, **kwargs):
raise AttributeError(f"'{type(self).__name__}' object is read-only")
pop = _readonly # type: ignore[assignment]
popitem = _readonly # type: ignore[assignment]
clear = _readonly # type: ignore[assignment]
update = _readonly # type: ignore[assignment]
setdefault = _readonly # type: ignore[assignment]
# From https://stackoverflow.com/a/59128615
def attributes(obj):
disallowed_names = {