Merge pull request #2821 from dokson/feature/drop-frozendict-dep
Drop frozendict hard dependency in favour of an internal fallback
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user