Skip to content

Commit 127b53e

Browse files
authored
Merge pull request #1693 from ranaroussi/main
sync main -> dev
2 parents 0f36f79 + 88525ab commit 127b53e

File tree

9 files changed

+69
-123
lines changed

9 files changed

+69
-123
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ body:
4242
4343
Provide the following as best you can:
4444
45+
- type: textarea
46+
id: summary
47+
attributes:
48+
label: "Describe bug"
49+
validations:
50+
required: true
51+
4552
- type: textarea
4653
id: code
4754
attributes:

CHANGELOG.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Change Log
22
===========
33

4+
0.2.29
5+
------
6+
- Fix pandas warning when retrieving quotes. #1672
7+
- Replace sqlite3 with peewee for 100% thread-safety #1675
8+
- Fix merging events with intraday prices #1684
9+
- Fix error when calling enable_debug_mode twice #1687
10+
- Price repair fixes #1688
11+
12+
0.2.28
13+
------
14+
- Fix TypeError: 'FastInfo' object is not callable #1636
15+
- Improve & fix price repair #1633 #1660
16+
- option_chain() also return underlying data #1606
17+
418
0.2.27
519
------
620
Bug fixes:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ To install `yfinance` using `conda`, see
258258
- [frozendict](https://pypi.org/project/frozendict) \>= 2.3.4
259259
- [beautifulsoup4](https://pypi.org/project/beautifulsoup4) \>= 4.11.1
260260
- [html5lib](https://pypi.org/project/html5lib) \>= 1.1
261+
- [peewee](https://pypi.org/project/peewee) \>= 3.16.2
261262

262263
#### Optional (if you want to use `pandas_datareader`)
263264

meta.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% set name = "yfinance" %}
2-
{% set version = "0.2.27" %}
2+
{% set version = "0.2.29" %}
33

44
package:
55
name: "{{ name|lower }}"
@@ -26,6 +26,7 @@ requirements:
2626
- frozendict >=2.3.4
2727
- beautifulsoup4 >=4.11.1
2828
- html5lib >=1.1
29+
- peewee >=3.16.2
2930
# - pycryptodome >=3.6.6
3031
- pip
3132
- python
@@ -41,6 +42,7 @@ requirements:
4142
- frozendict >=2.3.4
4243
- beautifulsoup4 >=4.11.1
4344
- html5lib >=1.1
45+
- peewee >=3.16.2
4446
# - pycryptodome >=3.6.6
4547
- python
4648

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pytz>=2022.5
88
frozendict>=2.3.4
99
beautifulsoup4>=4.11.1
1010
html5lib>=1.1
11+
peewee>=3.16.2

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
install_requires=['pandas>=1.3.0', 'numpy>=1.16.5',
6363
'requests>=2.31', 'multitasking>=0.0.7',
6464
'lxml>=4.9.1', 'appdirs>=1.4.4', 'pytz>=2022.5',
65-
'frozendict>=2.3.4',
65+
'frozendict>=2.3.4', 'peewee>=3.16.2',
6666
'beautifulsoup4>=4.11.1', 'html5lib>=1.1'],
6767
# Note: Pandas.read_html() needs html5lib & beautifulsoup4
6868
entry_points={

yfinance/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1736,7 +1736,6 @@ def get_info(self, proxy=None) -> dict:
17361736
data = self._quote.info
17371737
return data
17381738

1739-
@property
17401739
def get_fast_info(self, proxy=None):
17411740
if self._fast_info is None:
17421741
self._fast_info = FastInfo(self, proxy=proxy)

yfinance/utils.py

Lines changed: 41 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import logging
2828
import os as _os
2929
import re as _re
30-
import sqlite3 as _sqlite3
30+
import peewee as _peewee
3131
import sys as _sys
3232
import threading
3333
from functools import lru_cache
@@ -901,136 +901,60 @@ def __str__(self):
901901
# TimeZone cache related code
902902
# ---------------------------------
903903

904-
class _KVStore:
905-
"""Simple Sqlite backed key/value store, key and value are strings. Should be thread safe."""
906904

907-
def __init__(self, filename):
908-
self._cache_mutex = Lock()
909-
with self._cache_mutex:
910-
self.conn = _sqlite3.connect(filename, timeout=10, check_same_thread=False)
911-
self.conn.execute('pragma journal_mode=wal')
912-
try:
913-
self.conn.execute('create table if not exists "kv" (key TEXT primary key, value TEXT) without rowid')
914-
except Exception as e:
915-
if 'near "without": syntax error' in str(e):
916-
# "without rowid" requires sqlite 3.8.2. Older versions will raise exception
917-
self.conn.execute('create table if not exists "kv" (key TEXT primary key, value TEXT)')
918-
else:
919-
raise
920-
self.conn.commit()
921-
_atexit.register(self.close)
922-
923-
def close(self):
924-
if self.conn is not None:
925-
with self._cache_mutex:
926-
self.conn.close()
927-
self.conn = None
928-
929-
def get(self, key: str) -> Union[str, None]:
930-
"""Get value for key if it exists else returns None"""
931-
try:
932-
item = self.conn.execute('select value from "kv" where key=?', (key,))
933-
except _sqlite3.IntegrityError as e:
934-
self.delete(key)
935-
return None
936-
if item:
937-
return next(item, (None,))[0]
938-
939-
def set(self, key: str, value: str) -> None:
940-
if value is None:
941-
self.delete(key)
942-
else:
943-
with self._cache_mutex:
944-
self.conn.execute('replace into "kv" (key, value) values (?,?)', (key, value))
945-
self.conn.commit()
946-
947-
def bulk_set(self, kvdata: Dict[str, str]):
948-
records = tuple(i for i in kvdata.items())
949-
with self._cache_mutex:
950-
self.conn.executemany('replace into "kv" (key, value) values (?,?)', records)
951-
self.conn.commit()
952-
953-
def delete(self, key: str):
954-
with self._cache_mutex:
955-
self.conn.execute('delete from "kv" where key=?', (key,))
956-
self.conn.commit()
905+
_cache_dir = _os.path.join(_ad.user_cache_dir(), "py-yfinance")
906+
DB_PATH = _os.path.join(_cache_dir, 'tkr-tz.db')
907+
db = _peewee.SqliteDatabase(DB_PATH, pragmas={'journal_mode': 'wal', 'cache_size': -64})
908+
_tz_cache = None
957909

958910

959911
class _TzCacheException(Exception):
960912
pass
961913

962914

963-
class _TzCache:
964-
"""Simple sqlite file cache of ticker->timezone"""
965-
966-
def __init__(self):
967-
self._setup_cache_folder()
968-
# Must init db here, where is thread-safe
969-
try:
970-
self._tz_db = _KVStore(_os.path.join(self._db_dir, "tkr-tz.db"))
971-
except _sqlite3.DatabaseError as err:
972-
raise _TzCacheException(f"Error creating TzCache folder: '{self._db_dir}' reason: {err}")
973-
self._migrate_cache_tkr_tz()
974-
975-
def _setup_cache_folder(self):
976-
if not _os.path.isdir(self._db_dir):
977-
try:
978-
_os.makedirs(self._db_dir)
979-
except OSError as err:
980-
raise _TzCacheException(f"Error creating TzCache folder: '{self._db_dir}' reason: {err}")
981-
982-
elif not (_os.access(self._db_dir, _os.R_OK) and _os.access(self._db_dir, _os.W_OK)):
983-
raise _TzCacheException(f"Cannot read and write in TzCache folder: '{self._db_dir}'")
984-
985-
def lookup(self, tkr):
986-
return self.tz_db.get(tkr)
987-
988-
def store(self, tkr, tz):
989-
if tz is None:
990-
self.tz_db.delete(tkr)
991-
else:
992-
tz_db = self.tz_db.get(tkr)
993-
if tz_db is not None:
994-
if tz != tz_db:
995-
get_yf_logger().debug(f'{tkr}: Overwriting cached TZ "{tz_db}" with different TZ "{tz}"')
996-
self.tz_db.set(tkr, tz)
997-
else:
998-
self.tz_db.set(tkr, tz)
915+
class KV(_peewee.Model):
916+
key = _peewee.CharField(primary_key=True)
917+
value = _peewee.CharField(null=True)
918+
919+
class Meta:
920+
database = db
921+
without_rowid = True
999922

1000-
@property
1001-
def _db_dir(self):
1002-
global _cache_dir
1003-
return _os.path.join(_cache_dir, "py-yfinance")
1004923

1005-
@property
1006-
def tz_db(self):
1007-
return self._tz_db
924+
class _TzCache:
925+
def __init__(self):
926+
db.connect()
927+
db.create_tables([KV])
1008928

1009-
def _migrate_cache_tkr_tz(self):
1010-
"""Migrate contents from old ticker CSV-cache to SQLite db"""
1011-
old_cache_file_path = _os.path.join(self._db_dir, "tkr-tz.csv")
929+
old_cache_file_path = _os.path.join(_cache_dir, "tkr-tz.csv")
930+
if _os.path.isfile(old_cache_file_path):
931+
_os.remove(old_cache_file_path)
1012932

1013-
if not _os.path.isfile(old_cache_file_path):
933+
def lookup(self, key):
934+
try:
935+
return KV.get(KV.key == key).value
936+
except KV.DoesNotExist:
1014937
return None
938+
939+
def store(self, key, value):
1015940
try:
1016-
df = _pd.read_csv(old_cache_file_path, index_col="Ticker", on_bad_lines="skip")
1017-
except _pd.errors.EmptyDataError:
1018-
_os.remove(old_cache_file_path)
1019-
except TypeError:
1020-
_os.remove(old_cache_file_path)
1021-
else:
1022-
# Discard corrupt data:
1023-
df = df[~df["Tz"].isna().to_numpy()]
1024-
df = df[~(df["Tz"] == '').to_numpy()]
1025-
df = df[~df.index.isna()]
1026-
if not df.empty:
1027-
try:
1028-
self.tz_db.bulk_set(df.to_dict()['Tz'])
1029-
except Exception as e:
1030-
# Ignore
1031-
pass
941+
if value is None:
942+
q = KV.delete().where(KV.key == key)
943+
q.execute()
944+
return
945+
with db.atomic():
946+
KV.insert(key=key, value=value).execute()
947+
except IntegrityError:
948+
# Integrity error means the key already exists. Try updating the key.
949+
old_value = self.lookup(key)
950+
if old_value != value:
951+
get_yf_logger().debug(f"Value for key {key} changed from {old_value} to {value}.")
952+
with db.atomic():
953+
q = KV.update(value=value).where(KV.key == key)
954+
q.execute()
1032955

1033-
_os.remove(old_cache_file_path)
956+
def close(self):
957+
db.close()
1034958

1035959

1036960
class _TzCacheDummy:
@@ -1068,9 +992,7 @@ def get_tz_cache():
1068992
return _tz_cache
1069993

1070994

1071-
_cache_dir = _ad.user_cache_dir()
1072995
_cache_init_lock = Lock()
1073-
_tz_cache = None
1074996

1075997

1076998
def set_tz_cache_location(cache_dir: str):

yfinance/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = "0.2.27"
1+
version = "0.2.29"

0 commit comments

Comments
 (0)