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
|
multitasking>=0.0.7
|
||||||
platformdirs>=2.0.0
|
platformdirs>=2.0.0
|
||||||
pytz>=2022.5
|
pytz>=2022.5
|
||||||
frozendict>=2.3.4
|
|
||||||
beautifulsoup4>=4.11.1
|
beautifulsoup4>=4.11.1
|
||||||
lxml>=4.9.0
|
lxml>=4.9.0
|
||||||
peewee>=3.16.2
|
peewee>=3.16.2
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ setup(
|
|||||||
install_requires=['pandas>=1.3.0', 'numpy>=1.16.5',
|
install_requires=['pandas>=1.3.0', 'numpy>=1.16.5',
|
||||||
'requests>=2.31', 'multitasking>=0.0.7',
|
'requests>=2.31', 'multitasking>=0.0.7',
|
||||||
'platformdirs>=2.0.0', 'pytz>=2022.5',
|
'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',
|
'beautifulsoup4>=4.11.1', 'curl_cffi>=0.15',
|
||||||
'protobuf>=3.19.0', 'websockets>=13.0'],
|
'protobuf>=3.19.0', 'websockets>=13.0'],
|
||||||
extras_require={
|
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
|
from bs4 import BeautifulSoup
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from frozendict import frozendict
|
|
||||||
|
|
||||||
from . import utils, cache
|
from . import utils, cache
|
||||||
|
from .utils import frozendict
|
||||||
from .config import YfConfig
|
from .config import YfConfig
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,34 @@ from yfinance import const
|
|||||||
from yfinance.exceptions import YFException
|
from yfinance.exceptions import YFException
|
||||||
from yfinance.config import YfConfig
|
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
|
# From https://stackoverflow.com/a/59128615
|
||||||
def attributes(obj):
|
def attributes(obj):
|
||||||
disallowed_names = {
|
disallowed_names = {
|
||||||
|
|||||||
Reference in New Issue
Block a user