Compare commits

...

26 Commits

Author SHA1 Message Date
ValueRaider
5812aa6a69 Version 0.2.63 2025-06-12 09:41:44 +01:00
ValueRaider
ea810b4b35 Merge pull request #2531 from ranaroussi/hotfix/download-isin
Fix download()+ISIN
2025-06-12 09:41:00 +01:00
ValueRaider
53bce46929 Fix download()+ISIN 2025-06-12 08:54:57 +01:00
ValueRaider
0eb11c56b0 Version 0.2.62 2025-06-08 16:08:37 +01:00
ValueRaider
2981893f30 Merge pull request #2525 from ranaroussi/dev
sync dev -> main
2025-06-08 16:02:33 +01:00
ValueRaider
3250386136 Merge pull request #2402 from cclauss/patch-1
GitHub Action: Replace archived ruff action with official action
2025-06-08 14:58:43 +01:00
ValueRaider
aa606642c0 Merge branch 'main' into patch-1 2025-06-08 14:58:10 +01:00
ValueRaider
cc0b03efd1 Merge pull request #2509 from cole-st-john/adjusting_max_period_logic2
adjusting for processing time in max period (reduced)
2025-06-08 12:52:55 +01:00
ValueRaider
39dd87080d Merge pull request #2523 from ranaroussi/feature/print_once_replace_with_warnings
Replace 'print_once' with warnings.
2025-06-07 22:27:18 +01:00
ValueRaider
72a2fd8955 Merge pull request #2516 from ranaroussi/feature/isin-cache
Feature: ISIN cache
2025-06-07 21:21:09 +01:00
ValueRaider
dd62ce510e Replace 'print_once' with warnings. Remove 'basic_info'. 2025-06-07 21:19:38 +01:00
ValueRaider
61ceb2b1a8 Merge branch 'dev' into feature/isin-cache 2025-06-07 18:03:47 +01:00
ValueRaider
5d7a298239 Merge pull request #2514 from ranaroussi/fix/isin-proxy-msg
Fix ISIN proxy
2025-05-27 21:05:46 +01:00
ValueRaider
0192d2e194 Prune old ISINs from cache, because Yahoo won't serve 2025-05-25 12:27:57 +01:00
ValueRaider
8c6eb1afeb ISIN->symbol cache 2025-05-25 12:09:51 +01:00
ValueRaider
ef60663bc2 Fix ISIN proxy 2025-05-25 11:48:42 +01:00
cole-st-john
d15cf378a1 Fix 'max' period
- add 5 sec buffer for processing time
- add '2m' interval
- increase '1m' max to 8 days
2025-05-21 20:28:15 +01:00
ValueRaider
a9282e5739 Fix ruff 2025-05-17 11:25:10 +01:00
ValueRaider
a506838c3c Merge pull request #2504 from ranaroussi/main
sync main -> dev
2025-05-17 11:19:36 +01:00
ValueRaider
f716eec5fe Little fixes for tests & proxy msg 2025-05-14 21:41:17 +01:00
ValueRaider
e769570f33 Tidy CONTRIBUTING.md 2025-05-14 21:15:16 +01:00
ValueRaider
3bc6bacf56 Replace requests.HTTPError with curl_cffi 2025-05-13 21:25:25 +01:00
ValueRaider
7db82f3496 Merge pull request #2491 from vsukhoml/crumb
Fix for rate limit during getting crumb.
2025-05-13 20:52:00 +01:00
ValueRaider
4ac5cd87b3 Docs: simplify dev guide 2025-05-13 20:48:33 +01:00
Vadim Sukhomlinov
4a91008c09 Fix for rate limit during getting crumb.
Address #2441, #2480
Don't check for cached crumb to be wrong since this shall not happen with a new flow.
2025-05-13 12:34:23 -07:00
Christian Clauss
b8ae8f317f GitHub Action: Replace archived ruff action with official action
https://github.com/ChartBoost/ruff-action has been archived so replace it with the official https://github.com/astral-sh/ruff-action from the creators of ruff.
2025-04-15 17:16:22 +02:00
30 changed files with 550 additions and 344 deletions

View File

@@ -9,7 +9,7 @@ jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: check . --exclude yfinance/pricing_pb2.py

View File

@@ -1,6 +1,22 @@
Change Log
===========
0.2.63
------
Fix download(ISIN) # 2531
0.2.62
------
Fix prices 'period=max' sometimes failing # 2509
ISIN cache #2516
Proxy:
- fix false 'proxy deprecated' messages
- fix ISIN + proxy #2514
- replace print_once with warnings #2523
Error handling:
- detect rate-limit during crumb fetch #2491
- replace requests.HTTPError with curl_cffi
0.2.61
------
Fix ALL type hints in websocket #2493

View File

@@ -1,37 +1,23 @@
# Contributing
> [!NOTE]
> This is a brief guide to contributing to yfinance.
> For more information See the [Developer Guide](https://ranaroussi.github.io/yfinance/development) for more information.
yfinance relies on the community to investigate bugs and contribute code.
## Changes
The list of changes can be found in the [Changelog](https://github.com/ranaroussi/yfinance/blob/main/CHANGELOG.rst)
## Running a branch
```bash
pip install git+ranaroussi/yfinance.git@dev # dev branch
```
For more information, see the [Developer Guide](https://ranaroussi.github.io/yfinance/development/running.html).
This is a quick short guide, full guide at https://ranaroussi.github.io/yfinance/development/index.html
## Branches
YFinance uses a two-layer branch model:
* **dev**: new features & some bug-fixes merged here, tested together, conflicts fixed, etc.
* **dev**: new features & most bug-fixes merged here, tested together, conflicts fixed, etc.
* **main**: stable branch where PIP releases are created.
> [!NOTE]
> By default, branches target **main**, but most contributions should target **dev**.
> Direct merges to **main** are allowed if:
> * `yfinance` is massively broken
> * Part of `yfinance` is broken, and the fix is simple and isolated
> * Not updating the code (e.g. docs)
## Running a branch
> [!NOTE]
> For more information, see the [Developer Guide](https://ranaroussi.github.io/yfinance/development/branches.html).
```bash
pip install git+ranaroussi/yfinance.git@dev # <- dev branch
```
https://ranaroussi.github.io/yfinance/development/running.html
### I'm a GitHub newbie, how do I contribute code?
@@ -39,39 +25,36 @@ YFinance uses a two-layer branch model:
2. Implement your change in your fork, ideally in a specific branch
3. Create a Pull Request, from your fork to this project. If addressing an Issue, link to it
3. Create a [Pull Request](https://github.com/ranaroussi/yfinance/pulls), from your fork to this project. If addressing an Issue, link to it
> [!NOTE]
> See the [Developer Guide](https://ranaroussi.github.io/yfinance/development/contributing.html) for more information.
### [How to download & run a GitHub version of yfinance](#Running-a-branch)
https://ranaroussi.github.io/yfinance/development/code.html
## Documentation website
The new docs website [ranaroussi.github.io/yfinance/index.html](https://ranaroussi.github.io/yfinance/index.html) is generated automatically from code.
The new docs website is generated automatically from code. https://ranaroussi.github.io/yfinance/index.html
> [!NOTE]
> See the [Developer Guide](https://ranaroussi.github.io/yfinance/development/documentation.html) for more information
> Including how to build and run the docs locally.
Remember to updates docs when you change code, and check docs locally.
https://ranaroussi.github.io/yfinance/development/documentation.html
## Git tricks
Help keep the Git commit history and [network graph](https://github.com/ranaroussi/yfinance/network) compact:
* got a long descriptive commit message? `git commit -m "short sentence summary" -m "full commit message"`
* combine multiple commits into 1 with `git squash`
* `git rebase` is your friend: change base branch, or "merge in" updates
https://ranaroussi.github.io/yfinance/development/code.html#git-stuff
## Unit tests
Tests have been written using the built-in Python module `unittest`. Examples:
#### Run all tests: `python -m unittest discover -s tests`
* Run all tests: `python -m unittest discover -s tests`
> [!NOTE]
>
> See the [Developer Guide](https://ranaroussi.github.io/yfinance/development/testing.html) for more information.
https://ranaroussi.github.io/yfinance/development/testing.html
## Git stuff
### commits
To keep the Git commit history and [network graph](https://github.com/ranaroussi/yfinance/network) compact please follow these two rules:
* For long commit messages use this: `git commit -m "short sentence summary" -m "full commit message"`
* `squash` tiny/negligible commits back with meaningful commits, or to combine successive related commits
> [!NOTE]
> See the [Developer Guide](https://ranaroussi.github.io/yfinance/development/contributing.html#GIT-STUFF) for more information.
> See the [Developer Guide](https://ranaroussi.github.io/yfinance/development/contributing.html#GIT-STUFF) for more information.

View File

@@ -1,41 +0,0 @@
********
Branches
********
To support rapid development without breaking stable versions, this project uses a two-layer branch model:
.. image:: assets/branches.png
:alt: Branching Model
`Inspiration <https://miro.medium.com/max/700/1*2YagIpX6LuauC3ASpwHekg.png>`_
- **dev**: New features and some bug fixes are merged here. This branch allows collective testing, conflict resolution, and further stabilization before merging into the stable branch.
- **main**: Stable branch where PIP releases are created.
By default, branches target **main**, but most contributions should target **dev**.
**Exceptions**:
Direct merges to **main** are allowed if:
- `yfinance` is massively broken
- Part of `yfinance` is broken, and the fix is simple and isolated
- Not updating the code (e.g. docs)
Rebasing
--------
If asked to move your branch from **main** to **dev**:
1. Ensure all relevant branches are pulled.
2. Run:
.. code-block:: bash
git checkout {branch}
git rebase --onto dev main {brach}
git push --force-with-lease origin {branch}
Running a branch
----------------
Please see `this page </development/running>`_.

View File

@@ -2,7 +2,30 @@
Code
****
1. Fork the repository on GitHub. If already forked, remember to `Sync fork`
To support rapid development without breaking stable versions, this project uses a two-layer branch model:
.. image:: assets/branches.png
:alt: Branching Model
`Inspiration <https://miro.medium.com/max/700/1*2YagIpX6LuauC3ASpwHekg.png>`_
- **dev**: New features and some bug fixes are merged here. This branch allows collective testing, conflict resolution, and further stabilization before merging into the stable branch.
- **main**: Stable branch where PIP releases are created.
By default, branches target **main**, but most contributions should target **dev**.
**Exceptions**:
Direct merges to **main** are allowed if:
- `yfinance` is massively broken
- Part of `yfinance` is broken, and the fix is simple and isolated
- Not updating the code (e.g. docs)
Creating your branch
--------------------
1. Fork the repository on GitHub. If already forked, remember to ``Sync fork``
2. Clone your forked repository:
.. code-block:: bash
@@ -15,39 +38,26 @@ Code
git checkout {base e.g. dev}
git pull
git checkout -b {branch}
git checkout -b {your branch}
4. Make your changes, commit them, and push your branch to GitHub. To keep the commit history and `network graph <https://github.com/ranaroussi/yfinance/network>`_ compact:
- for long commit messages use this format. Your long message can be multiple lines (tip: copy-paste):
.. code-block:: bash
git commit -m "short sentence summary" -m "full commit message"
- **git squash** tiny or negligible commits with meaningful ones, or to combine successive related commits. `git squash guide <https://docs.gitlab.com/ee/topics/git/git_rebase.html#interactive-rebase>`_
.. code-block:: bash
git rebase -i HEAD~2
git push --force-with-lease origin {branch}
5. If your branch is old and missing important updates in base branch, then instead of merging in, do a git rebase. This keeps your branch history clean. E.g.
4. Make your changes, commit them, and push your branch to GitHub. To keep the commit history and `network graph <https://github.com/ranaroussi/yfinance/network>`_ compact, give your commits a very short summary then description:
.. code-block:: bash
git checkout {base e.g. dev}
git pull
git checkout {branch}
git rebase {base}
git push --force-with-lease origin {branch}
git commit -m "short sentence summary" -m "full commit message"
# Long message can be multiple lines (tip: copy-paste)
6. `Open a pull request on Github <https://github.com/ranaroussi/yfinance/pulls>`_.
More Git stuff
Running a branch
----------------
Please see `this page </development/running>`_.
Git stuff
---------
- ``git rebase``. You might be asked to move your branch from ``main`` to ``dev``. Important to update **all** relevant branches.
- You might be asked to move your branch from ``main`` to ``dev``. This is a ``git rebase``. Remember to update **all** branches involved.
.. code-block:: bash
@@ -56,9 +66,25 @@ More Git stuff
git pull
git checkout dev
git pull
# rebase:
git checkout {branch}
# rebase from main to dev:
git checkout {your branch}
git pull
git rebase --onto dev main {branch}
git push --force-with-lease origin {branch}
git rebase --onto dev main {your branch}
git push --force-with-lease origin {your branch}
- ``git rebase`` can also be used to update your branch with new commits from base, but without adding a commit to your branch history like git merge does. This keeps history clean and avoids future merge problems.
.. code-block:: bash
git checkout {base branch e.g. dev}
git pull
git checkout {your branch}
git rebase {base}
git push --force-with-lease origin {your branch}
- ``git squash`` tiny or negligible commits with meaningful ones, or to combine successive related commits. `git squash guide <https://docs.gitlab.com/ee/topics/git/git_rebase.html#interactive-rebase>`_
.. code-block:: bash
git rebase -i HEAD~2
git push --force-with-lease origin {your branch}

View File

@@ -8,7 +8,6 @@ yfinance relies on the community to investigate bugs and contribute code. Here's
:maxdepth: 1
code
running
documentation
branches
testing
running
testing

View File

@@ -1,5 +1,5 @@
{% set name = "yfinance" %}
{% set version = "0.2.61" %}
{% set version = "0.2.63" %}
package:
name: "{{ name|lower }}"

View File

@@ -5,8 +5,8 @@ import datetime as _dt
import sys
import os
import yfinance
from requests_ratelimiter import LimiterSession
from pyrate_limiter import Duration, RequestRate, Limiter
# from requests_ratelimiter import LimiterSession
# from pyrate_limiter import Duration, RequestRate, Limiter
_parent_dp = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
_src_dp = _parent_dp
@@ -25,12 +25,15 @@ if os.path.isdir(testing_cache_dirpath):
import shutil
shutil.rmtree(testing_cache_dirpath)
# Setup a session to only rate-limit
history_rate = RequestRate(1, Duration.SECOND)
limiter = Limiter(history_rate)
session_gbl = LimiterSession(limiter=limiter)
# Since switching to curl_cffi, the requests_ratelimiter|cache won't work.
session_gbl = None
# Use this instead if you also want caching:
# # Setup a session to only rate-limit
# history_rate = RequestRate(1, Duration.SECOND)
# limiter = Limiter(history_rate)
# session_gbl = LimiterSession(limiter=limiter)
# # Use this instead if you also want caching:
# from requests_cache import CacheMixin, SQLiteCache
# from requests_ratelimiter import LimiterMixin
# from requests import Session

View File

@@ -18,9 +18,9 @@ from yfinance.exceptions import YFPricesMissingError, YFInvalidPeriodError, YFNo
import unittest
import requests_cache
# import requests_cache
from typing import Union, Any, get_args, _GenericAlias
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
# from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
ticker_attributes = (
("major_holders", pd.DataFrame),
@@ -284,36 +284,37 @@ class TestTickerHistory(unittest.TestCase):
else:
self.assertIsInstance(data.columns, pd.MultiIndex)
def test_no_expensive_calls_introduced(self):
"""
Make sure calling history to get price data has not introduced more calls to yahoo than absolutely necessary.
As doing other type of scraping calls than "query2.finance.yahoo.com/v8/finance/chart" to yahoo website
will quickly trigger spam-block when doing bulk download of history data.
"""
symbol = "GOOGL"
period = "1y"
with requests_cache.CachedSession(backend="memory") as session:
ticker = yf.Ticker(symbol, session=session)
ticker.history(period=period)
actual_urls_called = [r.url for r in session.cache.filter()]
# Hopefully one day we find an equivalent "requests_cache" that works with "curl_cffi"
# def test_no_expensive_calls_introduced(self):
# """
# Make sure calling history to get price data has not introduced more calls to yahoo than absolutely necessary.
# As doing other type of scraping calls than "query2.finance.yahoo.com/v8/finance/chart" to yahoo website
# will quickly trigger spam-block when doing bulk download of history data.
# """
# symbol = "GOOGL"
# period = "1y"
# with requests_cache.CachedSession(backend="memory") as session:
# ticker = yf.Ticker(symbol, session=session)
# ticker.history(period=period)
# actual_urls_called = [r.url for r in session.cache.filter()]
# Remove 'crumb' argument
for i in range(len(actual_urls_called)):
u = actual_urls_called[i]
parsed_url = urlparse(u)
query_params = parse_qs(parsed_url.query)
query_params.pop('crumb', None)
query_params.pop('cookie', None)
u = urlunparse(parsed_url._replace(query=urlencode(query_params, doseq=True)))
actual_urls_called[i] = u
actual_urls_called = tuple(actual_urls_called)
# # Remove 'crumb' argument
# for i in range(len(actual_urls_called)):
# u = actual_urls_called[i]
# parsed_url = urlparse(u)
# query_params = parse_qs(parsed_url.query)
# query_params.pop('crumb', None)
# query_params.pop('cookie', None)
# u = urlunparse(parsed_url._replace(query=urlencode(query_params, doseq=True)))
# actual_urls_called[i] = u
# actual_urls_called = tuple(actual_urls_called)
expected_urls = [
f"https://query2.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=1d", # ticker's tz
f"https://query2.finance.yahoo.com/v8/finance/chart/{symbol}?events=div%2Csplits%2CcapitalGains&includePrePost=False&interval=1d&range={period}"
]
for url in actual_urls_called:
self.assertTrue(url in expected_urls, f"Unexpected URL called: {url}")
# expected_urls = [
# f"https://query2.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=1d", # ticker's tz
# f"https://query2.finance.yahoo.com/v8/finance/chart/{symbol}?events=div%2Csplits%2CcapitalGains&includePrePost=False&interval=1d&range={period}"
# ]
# for url in actual_urls_called:
# self.assertTrue(url in expected_urls, f"Unexpected URL called: {url}")
def test_dividends(self):
data = self.ticker.dividends
@@ -889,8 +890,6 @@ class TestTickerAnalysts(unittest.TestCase):
data = self.ticker.upgrades_downgrades
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertTrue(len(data.columns) == 4, "data has wrong number of columns")
self.assertCountEqual(data.columns.values.tolist(), ['Firm', 'ToGrade', 'FromGrade', 'Action'], "data has wrong column names")
self.assertIsInstance(data.index, pd.DatetimeIndex, "data has wrong index type")
data_cached = self.ticker.upgrades_downgrades
@@ -1000,7 +999,6 @@ class TestTickerInfo(unittest.TestCase):
self.assertIsInstance(data, dict, "data has wrong type")
expected_keys = ['industry', 'currentPrice', 'exchange', 'floatShares', 'companyOfficers', 'bid']
for k in expected_keys:
print(k)
self.assertIn("symbol", data.keys(), f"Did not find expected key '{k}' in info dict")
self.assertEqual(self.symbols[0], data["symbol"], "Wrong symbol value in info dict")

View File

@@ -66,17 +66,22 @@ class TickerBase:
if self.ticker == "":
raise ValueError("Empty ticker name")
self._data: YfData = YfData(session=session)
if proxy is not _SENTINEL_:
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
# accept isin as ticker
if utils.is_isin(self.ticker):
isin = self.ticker
self.ticker = utils.get_ticker_by_isin(self.ticker, None, session)
c = cache.get_isin_cache()
self.ticker = c.lookup(isin)
if not self.ticker:
self.ticker = utils.get_ticker_by_isin(isin)
if self.ticker == "":
raise ValueError(f"Invalid ISIN number: {isin}")
self._data: YfData = YfData(session=session)
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
self._data._set_proxy(proxy)
if self.ticker:
c.store(isin, self.ticker)
# self._price_history = PriceHistory(self._data, self.ticker)
self._price_history = None # lazy-load
@@ -176,7 +181,7 @@ class TickerBase:
Columns: period strongBuy buy hold sell strongSell
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._quote.recommendations
@@ -186,7 +191,7 @@ class TickerBase:
def get_recommendations_summary(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self.get_recommendations(as_dict=as_dict)
@@ -198,7 +203,7 @@ class TickerBase:
Columns: firm toGrade fromGrade action
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._quote.upgrades_downgrades
@@ -208,21 +213,21 @@ class TickerBase:
def get_calendar(self, proxy=_SENTINEL_) -> dict:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._quote.calendar
def get_sec_filings(self, proxy=_SENTINEL_) -> dict:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._quote.sec_filings
def get_major_holders(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._holders.major
@@ -232,7 +237,7 @@ class TickerBase:
def get_institutional_holders(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._holders.institutional
@@ -243,7 +248,7 @@ class TickerBase:
def get_mutualfund_holders(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._holders.mutualfund
@@ -254,7 +259,7 @@ class TickerBase:
def get_insider_purchases(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._holders.insider_purchases
@@ -265,7 +270,7 @@ class TickerBase:
def get_insider_transactions(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._holders.insider_transactions
@@ -276,7 +281,7 @@ class TickerBase:
def get_insider_roster_holders(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._holders.insider_roster
@@ -287,7 +292,7 @@ class TickerBase:
def get_info(self, proxy=_SENTINEL_) -> dict:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._quote.info
@@ -295,21 +300,16 @@ class TickerBase:
def get_fast_info(self, proxy=_SENTINEL_):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
if self._fast_info is None:
self._fast_info = FastInfo(self)
return self._fast_info
@property
def basic_info(self):
warnings.warn("'Ticker.basic_info' is deprecated and will be removed in future, Switch to 'Ticker.fast_info'", DeprecationWarning)
return self.fast_info
def get_sustainability(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._quote.sustainability
@@ -319,7 +319,7 @@ class TickerBase:
def get_analyst_price_targets(self, proxy=_SENTINEL_) -> dict:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
"""
@@ -330,7 +330,7 @@ class TickerBase:
def get_earnings_estimate(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
"""
@@ -342,7 +342,7 @@ class TickerBase:
def get_revenue_estimate(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
"""
@@ -354,7 +354,7 @@ class TickerBase:
def get_earnings_history(self, proxy=_SENTINEL_, as_dict=False):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
"""
@@ -370,7 +370,7 @@ class TickerBase:
Columns: current 7daysAgo 30daysAgo 60daysAgo 90daysAgo
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._analysis.eps_trend
@@ -382,7 +382,7 @@ class TickerBase:
Columns: upLast7days upLast30days downLast7days downLast30days
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._analysis.eps_revisions
@@ -394,7 +394,7 @@ class TickerBase:
Columns: stock industry sector index
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._analysis.growth_estimates
@@ -411,7 +411,7 @@ class TickerBase:
Default is "yearly"
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
if self._fundamentals.earnings is None:
@@ -438,7 +438,7 @@ class TickerBase:
Default is "yearly"
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._fundamentals.financials.get_income_time_series(freq=freq)
@@ -452,14 +452,14 @@ class TickerBase:
def get_incomestmt(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self.get_income_stmt(proxy, as_dict, pretty, freq)
def get_financials(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self.get_income_stmt(proxy, as_dict, pretty, freq)
@@ -478,7 +478,7 @@ class TickerBase:
Default is "yearly"
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
@@ -493,7 +493,7 @@ class TickerBase:
def get_balancesheet(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self.get_balance_sheet(proxy, as_dict, pretty, freq)
@@ -512,7 +512,7 @@ class TickerBase:
Default is "yearly"
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
@@ -527,37 +527,37 @@ class TickerBase:
def get_cashflow(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self.get_cash_flow(proxy, as_dict, pretty, freq)
def get_dividends(self, proxy=_SENTINEL_, period="max") -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._lazy_load_price_history().get_dividends(period=period)
def get_capital_gains(self, proxy=_SENTINEL_, period="max") -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._lazy_load_price_history().get_capital_gains(period=period)
def get_splits(self, proxy=_SENTINEL_, period="max") -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._lazy_load_price_history().get_splits(period=period)
def get_actions(self, proxy=_SENTINEL_, period="max") -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._lazy_load_price_history().get_actions(period=period)
def get_shares(self, proxy=_SENTINEL_, as_dict=False) -> Union[pd.DataFrame, dict]:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
data = self._fundamentals.shares
@@ -570,7 +570,7 @@ class TickerBase:
logger = utils.get_yf_logger()
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
# Process dates
@@ -624,7 +624,7 @@ class TickerBase:
def get_isin(self, proxy=_SENTINEL_) -> Optional[str]:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
# *** experimental ***
@@ -670,7 +670,7 @@ class TickerBase:
logger = utils.get_yf_logger()
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
tab_queryrefs = {
@@ -722,7 +722,7 @@ class TickerBase:
logger = utils.get_yf_logger()
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
clamped_limit = min(limit, 100) # YF caps at 100, don't go higher
@@ -783,14 +783,14 @@ class TickerBase:
def get_history_metadata(self, proxy=_SENTINEL_) -> dict:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
return self._lazy_load_price_history().get_history_metadata(proxy)
def get_funds_data(self, proxy=_SENTINEL_) -> Optional[FundsData]:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
if not self._funds_data:

View File

@@ -3,13 +3,15 @@ from threading import Lock
import os as _os
import platformdirs as _ad
import atexit as _atexit
import datetime as _datetime
import datetime as _dt
import pickle as _pkl
from .utils import get_yf_logger
_cache_init_lock = Lock()
# --------------
# TimeZone cache
# --------------
@@ -105,7 +107,7 @@ _atexit.register(_TzDBManager.close_db)
tz_db_proxy = _peewee.Proxy()
class _KV(_peewee.Model):
class _TZ_KV(_peewee.Model):
key = _peewee.CharField(primary_key=True)
value = _peewee.CharField(null=True)
@@ -146,11 +148,11 @@ class _TzCache:
db.connect()
tz_db_proxy.initialize(db)
try:
db.create_tables([_KV])
db.create_tables([_TZ_KV])
except _peewee.OperationalError as e:
if 'WITHOUT' in str(e):
_KV._meta.without_rowid = False
db.create_tables([_KV])
_TZ_KV._meta.without_rowid = False
db.create_tables([_TZ_KV])
else:
raise
self.initialised = 1 # success
@@ -166,8 +168,8 @@ class _TzCache:
return None
try:
return _KV.get(_KV.key == key).value
except _KV.DoesNotExist:
return _TZ_KV.get(_TZ_KV.key == key).value
except _TZ_KV.DoesNotExist:
return None
def store(self, key, value):
@@ -185,18 +187,18 @@ class _TzCache:
return
try:
if value is None:
q = _KV.delete().where(_KV.key == key)
q = _TZ_KV.delete().where(_TZ_KV.key == key)
q.execute()
return
with db.atomic():
_KV.insert(key=key, value=value).execute()
_TZ_KV.insert(key=key, value=value).execute()
except _peewee.IntegrityError:
# Integrity error means the key already exists. Try updating the key.
old_value = self.lookup(key)
if old_value != value:
get_yf_logger().debug(f"Value for key {key} changed from {old_value} to {value}.")
with db.atomic():
q = _KV.update(value=value).where(_KV.key == key)
q = _TZ_KV.update(value=value).where(_TZ_KV.key == key)
q.execute()
@@ -301,16 +303,16 @@ class ISODateTimeField(_peewee.DateTimeField):
# because user discovered peewee allowed an invalid datetime
# to get written.
def db_value(self, value):
if value and isinstance(value, _datetime.datetime):
if value and isinstance(value, _dt.datetime):
return value.isoformat()
return super().db_value(value)
def python_value(self, value):
if value and isinstance(value, str) and 'T' in value:
return _datetime.datetime.fromisoformat(value)
return _dt.datetime.fromisoformat(value)
return super().python_value(value)
class _CookieSchema(_peewee.Model):
strategy = _peewee.CharField(primary_key=True)
fetch_date = ISODateTimeField(default=_datetime.datetime.now)
fetch_date = ISODateTimeField(default=_dt.datetime.now)
# Which cookie type depends on strategy
cookie_bytes = _peewee.BlobField()
@@ -374,7 +376,7 @@ class _CookieCache:
try:
data = _CookieSchema.get(_CookieSchema.strategy == strategy)
cookie = _pkl.loads(data.cookie_bytes)
return {'cookie':cookie, 'age':_datetime.datetime.now()-data.fetch_date}
return {'cookie':cookie, 'age':_dt.datetime.now()-data.fetch_date}
except _CookieSchema.DoesNotExist:
return None
@@ -415,6 +417,211 @@ def get_cookie_cache():
# --------------
# ISIN cache
# --------------
class _ISINCacheException(Exception):
pass
class _ISINCacheDummy:
"""Dummy cache to use if isin cache is disabled"""
def lookup(self, isin):
return None
def store(self, isin, tkr):
pass
@property
def tz_db(self):
return None
class _ISINCacheManager:
_isin_cache = None
@classmethod
def get_isin_cache(cls):
if cls._isin_cache is None:
with _cache_init_lock:
cls._initialise()
return cls._isin_cache
@classmethod
def _initialise(cls, cache_dir=None):
cls._isin_cache = _ISINCache()
class _ISINDBManager:
_db = None
_cache_dir = _os.path.join(_ad.user_cache_dir(), "py-yfinance")
@classmethod
def get_database(cls):
if cls._db is None:
cls._initialise()
return cls._db
@classmethod
def close_db(cls):
if cls._db is not None:
try:
cls._db.close()
except Exception:
# Must discard exceptions because Python trying to quit.
pass
@classmethod
def _initialise(cls, cache_dir=None):
if cache_dir is not None:
cls._cache_dir = cache_dir
if not _os.path.isdir(cls._cache_dir):
try:
_os.makedirs(cls._cache_dir)
except OSError as err:
raise _ISINCacheException(f"Error creating ISINCache folder: '{cls._cache_dir}' reason: {err}")
elif not (_os.access(cls._cache_dir, _os.R_OK) and _os.access(cls._cache_dir, _os.W_OK)):
raise _ISINCacheException(f"Cannot read and write in ISINCache folder: '{cls._cache_dir}'")
cls._db = _peewee.SqliteDatabase(
_os.path.join(cls._cache_dir, 'isin-tkr.db'),
pragmas={'journal_mode': 'wal', 'cache_size': -64}
)
@classmethod
def set_location(cls, new_cache_dir):
if cls._db is not None:
cls._db.close()
cls._db = None
cls._cache_dir = new_cache_dir
@classmethod
def get_location(cls):
return cls._cache_dir
# close DB when Python exists
_atexit.register(_ISINDBManager.close_db)
isin_db_proxy = _peewee.Proxy()
class _ISIN_KV(_peewee.Model):
key = _peewee.CharField(primary_key=True)
value = _peewee.CharField(null=True)
created_at = _peewee.DateTimeField(default=_dt.datetime.now)
class Meta:
database = isin_db_proxy
without_rowid = True
class _ISINCache:
def __init__(self):
self.initialised = -1
self.db = None
self.dummy = False
def get_db(self):
if self.db is not None:
return self.db
try:
self.db = _ISINDBManager.get_database()
except _ISINCacheException as err:
get_yf_logger().info(f"Failed to create ISINCache, reason: {err}. "
"ISINCache will not be used. "
"Tip: You can direct cache to use a different location with 'set_isin_cache_location(mylocation)'")
self.dummy = True
return None
return self.db
def initialise(self):
if self.initialised != -1:
return
db = self.get_db()
if db is None:
self.initialised = 0 # failure
return
db.connect()
isin_db_proxy.initialize(db)
try:
db.create_tables([_ISIN_KV])
except _peewee.OperationalError as e:
if 'WITHOUT' in str(e):
_ISIN_KV._meta.without_rowid = False
db.create_tables([_ISIN_KV])
else:
raise
self.initialised = 1 # success
def lookup(self, key):
if self.dummy:
return None
if self.initialised == -1:
self.initialise()
if self.initialised == 0: # failure
return None
try:
return _ISIN_KV.get(_ISIN_KV.key == key).value
except _ISIN_KV.DoesNotExist:
return None
def store(self, key, value):
if self.dummy:
return
if self.initialised == -1:
self.initialise()
if self.initialised == 0: # failure
return
db = self.get_db()
if db is None:
return
try:
if value is None:
q = _ISIN_KV.delete().where(_ISIN_KV.key == key)
q.execute()
return
# Remove existing rows with same value that are older than 1 week
one_week_ago = _dt.datetime.now() - _dt.timedelta(weeks=1)
old_rows_query = _ISIN_KV.delete().where(
(_ISIN_KV.value == value) &
(_ISIN_KV.created_at < one_week_ago)
)
old_rows_query.execute()
with db.atomic():
_ISIN_KV.insert(key=key, value=value).execute()
except _peewee.IntegrityError:
# Integrity error means the key already exists. Try updating the key.
old_value = self.lookup(key)
if old_value != value:
get_yf_logger().debug(f"Value for key {key} changed from {old_value} to {value}.")
with db.atomic():
q = _ISIN_KV.update(value=value, created_at=_dt.datetime.now()).where(_ISIN_KV.key == key)
q.execute()
def get_isin_cache():
return _ISINCacheManager.get_isin_cache()
# --------------
# Utils
# --------------
def set_cache_location(cache_dir: str):
"""
Sets the path to create the "py-yfinance" cache folder in.
@@ -425,6 +632,7 @@ def set_cache_location(cache_dir: str):
"""
_TzDBManager.set_location(cache_dir)
_CookieDBManager.set_location(cache_dir)
_ISINDBManager.set_location(cache_dir)
def set_tz_cache_location(cache_dir: str):
set_cache_location(cache_dir)

View File

@@ -223,6 +223,10 @@ class YfData(metaclass=SingletonMeta):
else:
crumb_response = self._session.get(**get_args)
self._crumb = crumb_response.text
if crumb_response.status_code == 429 or "Too Many Requests" in self._crumb:
utils.get_yf_logger().debug(f"Didn't receive crumb {self._crumb}")
raise YFRateLimitError()
if self._crumb is None or '<html>' in self._crumb:
utils.get_yf_logger().debug("Didn't receive crumb")
return None
@@ -327,6 +331,10 @@ class YfData(metaclass=SingletonMeta):
r = self._session.get(**get_args)
self._crumb = r.text
if r.status_code == 429 or "Too Many Requests" in self._crumb:
utils.get_yf_logger().debug(f"Didn't receive crumb {self._crumb}")
raise YFRateLimitError()
if self._crumb is None or '<html>' in self._crumb or self._crumb == '':
utils.get_yf_logger().debug("Didn't receive crumb")
return None
@@ -360,11 +368,11 @@ class YfData(metaclass=SingletonMeta):
@utils.log_indent_decorator
def get(self, url, params=None, timeout=30):
return self._make_request(url, request_method = self._session.get, params=params, timeout=timeout)
@utils.log_indent_decorator
def post(self, url, body, params=None, timeout=30):
return self._make_request(url, request_method = self._session.post, body=body, params=params, timeout=timeout)
@utils.log_indent_decorator
def _make_request(self, url, request_method, body=None, params=None, timeout=30):
# Important: treat input arguments as immutable.
@@ -394,7 +402,7 @@ class YfData(metaclass=SingletonMeta):
if body:
request_args['json'] = body
response = request_method(**request_args)
utils.get_yf_logger().debug(f'response code={response.status_code}')
if response.status_code >= 400:

View File

@@ -1,10 +1,11 @@
from abc import ABC, abstractmethod
from ..ticker import Ticker
import pandas as _pd
from typing import Dict, List, Optional
import warnings
from ..const import _QUERY1_URL_, _SENTINEL_
from ..data import YfData
from ..utils import print_once
from typing import Dict, List, Optional
import pandas as _pd
from ..ticker import Ticker
_QUERY_URL_ = f'{_QUERY1_URL_}/v1/finance'
@@ -26,7 +27,7 @@ class Domain(ABC):
self.session = session
self._data: YfData = YfData(session=session)
if proxy is not _SENTINEL_:
print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
self._name: Optional[str] = None

View File

@@ -1,12 +1,14 @@
from __future__ import print_function
from typing import Dict, Optional
import pandas as _pd
from typing import Dict, Optional
import warnings
from .. import utils
from ..const import _SENTINEL_
from ..data import YfData
from .domain import Domain, _QUERY_URL_
from .. import utils
from ..data import YfData
from ..const import _SENTINEL_
class Industry(Domain):
"""
@@ -20,8 +22,9 @@ class Industry(Domain):
session (optional): The session to use for requests.
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
YfData(session=session, proxy=proxy)
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
YfData(proxy=proxy)
YfData(session=session)
super(Industry, self).__init__(key, session)
self._query_url = f'{_QUERY_URL_}/industries/{self._key}'

View File

@@ -1,9 +1,9 @@
import datetime as dt
from ..data import YfData
from ..data import utils
from ..const import _QUERY1_URL_, _SENTINEL_
import json as _json
import warnings
from ..const import _QUERY1_URL_, _SENTINEL_
from ..data import utils, YfData
class Market:
def __init__(self, market:'str', session=None, proxy=_SENTINEL_, timeout=30):
@@ -13,7 +13,7 @@ class Market:
self._data = YfData(session=self.session)
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
self._logger = utils.get_yf_logger()

View File

@@ -1,13 +1,14 @@
from __future__ import print_function
from typing import Dict, Optional
from ..utils import dynamic_docstring, generate_list_table_from_dict
from ..const import SECTOR_INDUSTY_MAPPING, _SENTINEL_
import pandas as _pd
from typing import Dict, Optional
import warnings
from ..const import SECTOR_INDUSTY_MAPPING, _SENTINEL_
from ..data import YfData
from ..utils import dynamic_docstring, generate_list_table_from_dict, get_yf_logger
from .domain import Domain, _QUERY_URL_
from .. import utils
from ..data import YfData
class Sector(Domain):
"""
@@ -28,7 +29,7 @@ class Sector(Domain):
Map of sector and industry
"""
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
YfData(session=session, proxy=proxy)
super(Sector, self).__init__(key, session)
@@ -147,7 +148,7 @@ class Sector(Domain):
self._industries = self._parse_industries(data.get('industries', {}))
except Exception as e:
logger = utils.get_yf_logger()
logger = get_yf_logger()
logger.error(f"Failed to get sector data for '{self._key}' reason: {e}")
logger.debug("Got response: ")
logger.debug("-------------")

View File

@@ -20,8 +20,8 @@
#
import json as _json
import pandas as pd
import warnings
from . import utils
from .const import _QUERY1_URL_, _SENTINEL_
@@ -43,12 +43,12 @@ class Lookup:
:param raise_errors: Raise exceptions on error (default True).
"""
def __init__(self, query: str, session=None, proxy=None, timeout=30, raise_errors=True):
def __init__(self, query: str, session=None, proxy=_SENTINEL_, timeout=30, raise_errors=True):
self.session = session
self._data = YfData(session=self.session)
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
self.query = query

View File

@@ -25,6 +25,7 @@ import logging
import time as _time
import traceback
from typing import Union
import warnings
import multitasking as _multitasking
import pandas as _pd
@@ -93,9 +94,15 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
logger = utils.get_yf_logger()
session = session or requests.Session(impersonate="chrome")
# Ensure data initialised with session.
if proxy is not _SENTINEL_:
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=3)
YfData(proxy=proxy)
YfData(session=session)
if auto_adjust is None:
# Warn users that default has changed to True
utils.print_once("YF.download() has changed argument auto_adjust default to True")
warnings.warn("YF.download() has changed argument auto_adjust default to True", FutureWarning, stacklevel=3)
auto_adjust = True
if logger.isEnabledFor(logging.DEBUG):
@@ -127,7 +134,7 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
for ticker in tickers:
if utils.is_isin(ticker):
isin = ticker
ticker = utils.get_ticker_by_isin(ticker, session=session)
ticker = utils.get_ticker_by_isin(ticker)
shared._ISINS[ticker] = isin
_tickers_.append(ticker)
@@ -143,13 +150,6 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
shared._ERRORS = {}
shared._TRACEBACKS = {}
# Ensure data initialised with session.
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
YfData(session=session, proxy=proxy)
else:
YfData(session=session)
# download using threads
if threads:
if threads is True:

View File

@@ -1,17 +1,18 @@
import curl_cffi
import pandas as pd
import requests
import warnings
from yfinance import utils
from yfinance.data import YfData
from yfinance.const import quote_summary_valid_modules, _SENTINEL_
from yfinance.scrapers.quote import _QUOTE_SUMMARY_URL_
from yfinance.data import YfData
from yfinance.exceptions import YFException
from yfinance.scrapers.quote import _QUOTE_SUMMARY_URL_
class Analysis:
def __init__(self, data: YfData, symbol: str, proxy=_SENTINEL_):
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
data._set_proxy(proxy)
self._data = data
@@ -178,7 +179,7 @@ class Analysis:
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false", "symbol": self._symbol}
try:
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", params=params_dict)
except requests.exceptions.HTTPError as e:
except curl_cffi.requests.exceptions.HTTPError as e:
utils.get_yf_logger().error(str(e))
return None
return result

View File

@@ -12,7 +12,7 @@ class Fundamentals:
def __init__(self, data: YfData, symbol: str, proxy=const._SENTINEL_):
if proxy is not const._SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
data._set_proxy(proxy)
self._data = data
@@ -53,7 +53,7 @@ class Financials:
def get_income_time_series(self, freq="yearly", proxy=const._SENTINEL_) -> pd.DataFrame:
if proxy is not const._SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
res = self._income_time_series
@@ -63,7 +63,7 @@ class Financials:
def get_balance_sheet_time_series(self, freq="yearly", proxy=const._SENTINEL_) -> pd.DataFrame:
if proxy is not const._SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
res = self._balance_sheet_time_series
@@ -73,7 +73,7 @@ class Financials:
def get_cash_flow_time_series(self, freq="yearly", proxy=const._SENTINEL_) -> pd.DataFrame:
if proxy is not const._SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
res = self._cash_flow_time_series

View File

@@ -1,11 +1,11 @@
import pandas as pd
from yfinance.data import YfData
from yfinance.const import _BASE_URL_, _SENTINEL_
from yfinance.exceptions import YFDataException
from yfinance import utils
from typing import Dict, Optional
import warnings
from yfinance import utils
from yfinance.const import _BASE_URL_, _SENTINEL_
from yfinance.data import YfData
from yfinance.exceptions import YFDataException
_QUOTE_SUMMARY_URL_ = f"{_BASE_URL_}/v10/finance/quoteSummary/"
@@ -26,7 +26,7 @@ class FundsData:
self._data = data
self._symbol = symbol
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
# quoteType

View File

@@ -1,12 +1,13 @@
from curl_cffi import requests
from math import isclose
import bisect
import datetime as _datetime
import dateutil as _dateutil
import logging
import numpy as np
import pandas as pd
from math import isclose
import time as _time
import bisect
from curl_cffi import requests
import warnings
from yfinance import shared, utils
from yfinance.const import _BASE_URL_, _PRICE_COLNAMES_, _SENTINEL_
@@ -18,7 +19,7 @@ class PriceHistory:
self.ticker = ticker.upper()
self.tz = tz
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=5)
self._data._set_proxy(proxy)
self.session = session or requests.Session(impersonate="chrome")
@@ -77,7 +78,7 @@ class PriceHistory:
logger = utils.get_yf_logger()
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=5)
self._data._set_proxy(proxy)
interval_user = interval
@@ -133,13 +134,14 @@ class PriceHistory:
end = utils._parse_user_dt(end, tz)
if start is None:
if interval == "1m":
start = end - 604800 # 7 days
elif interval in ("5m", "15m", "30m", "90m"):
start = end - 691200 # 8 days
elif interval in ("2m", "5m", "15m", "30m", "90m"):
start = end - 5184000 # 60 days
elif interval in ("1h", '60m'):
elif interval in ("1h", "60m"):
start = end - 63072000 # 730 days
else:
start = end - 3122064000 # 99 years
start += 5 # allow for processing time
else:
start = utils._parse_user_dt(start, tz)
params = {"period1": start, "period2": end}
@@ -481,7 +483,7 @@ class PriceHistory:
def get_history_metadata(self, proxy=_SENTINEL_) -> dict:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=3)
self._data._set_proxy(proxy)
if self._history_metadata is None or 'tradingPeriods' not in self._history_metadata:
@@ -496,7 +498,7 @@ class PriceHistory:
def get_dividends(self, period="max", proxy=_SENTINEL_) -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=3)
self._data._set_proxy(proxy)
df = self._get_history_cache(period=period)
@@ -507,7 +509,7 @@ class PriceHistory:
def get_capital_gains(self, period="max", proxy=_SENTINEL_) -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=3)
self._data._set_proxy(proxy)
df = self._get_history_cache(period=period)
@@ -518,7 +520,7 @@ class PriceHistory:
def get_splits(self, period="max", proxy=_SENTINEL_) -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=3)
self._data._set_proxy(proxy)
df = self._get_history_cache(period=period)
@@ -529,7 +531,7 @@ class PriceHistory:
def get_actions(self, period="max", proxy=_SENTINEL_) -> pd.Series:
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=3)
self._data._set_proxy(proxy)
df = self._get_history_cache(period=period)

View File

@@ -1,9 +1,10 @@
import curl_cffi
import pandas as pd
import requests
import warnings
from yfinance import utils
from yfinance.data import YfData
from yfinance.const import _BASE_URL_, _SENTINEL_
from yfinance.data import YfData
from yfinance.exceptions import YFDataException
_QUOTE_SUMMARY_URL_ = f"{_BASE_URL_}/v10/finance/quoteSummary"
@@ -15,7 +16,7 @@ class Holders:
self._data = data
self._symbol = symbol
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
data._set_proxy(proxy)
self._major = None
@@ -73,7 +74,7 @@ class Holders:
def _fetch_and_parse(self):
try:
result = self._fetch()
except requests.exceptions.HTTPError as e:
except curl_cffi.requests.exceptions.HTTPError as e:
utils.get_yf_logger().error(str(e))
self._major = pd.DataFrame()

View File

@@ -1,13 +1,13 @@
import curl_cffi
import datetime
import json
import numpy as _np
import pandas as pd
import requests
import warnings
from yfinance import utils
from yfinance.data import YfData
from yfinance.const import quote_summary_valid_modules, _BASE_URL_, _QUERY1_URL_, _SENTINEL_
from yfinance.data import YfData
from yfinance.exceptions import YFDataException, YFException
info_retired_keys_price = {"currentPrice", "dayHigh", "dayLow", "open", "previousClose", "volume", "volume24Hr"}
@@ -29,7 +29,7 @@ class FastInfo:
def __init__(self, tickerBaseObject, proxy=_SENTINEL_):
self._tkr = tickerBaseObject
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._tkr._data._set_proxy(proxy)
self._prices_1y = None
@@ -490,7 +490,7 @@ class Quote:
self._data = data
self._symbol = symbol
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
self._info = None
@@ -588,7 +588,7 @@ class Quote:
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false", "symbol": self._symbol}
try:
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", params=params_dict)
except requests.exceptions.HTTPError as e:
except curl_cffi.requests.exceptions.HTTPError as e:
utils.get_yf_logger().error(str(e))
return None
return result
@@ -597,7 +597,7 @@ class Quote:
params_dict = {"symbols": self._symbol, "formatted": "false"}
try:
result = self._data.get_raw_json(f"{_QUERY1_URL_}/v7/finance/quote?", params=params_dict)
except requests.exceptions.HTTPError as e:
except curl_cffi.requests.exceptions.HTTPError as e:
utils.get_yf_logger().error(str(e))
return None
return result

View File

@@ -1,14 +1,14 @@
from .query import EquityQuery as EqyQy
from .query import FundQuery as FndQy
from .query import QueryBase, EquityQuery, FundQuery
import curl_cffi
from typing import Union
import warnings
from yfinance.const import _QUERY1_URL_, _SENTINEL_
from yfinance.data import YfData
from ..utils import dynamic_docstring, generate_list_table_from_dict_universal
from ..utils import dynamic_docstring, generate_list_table_from_dict_universal, print_once
from typing import Union
import requests
from .query import EquityQuery as EqyQy
from .query import FundQuery as FndQy
from .query import QueryBase, EquityQuery, FundQuery
_SCREENER_URL_ = f"{_QUERY1_URL_}/v1/finance/screener"
_PREDEFINED_URL_ = f"{_SCREENER_URL_}/predefined/saved"
@@ -112,7 +112,7 @@ def screen(query: Union[str, EquityQuery, FundQuery],
"""
if proxy is not _SENTINEL_:
print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
_data = YfData(session=session, proxy=proxy)
else:
_data = YfData(session=session)
@@ -157,7 +157,7 @@ def screen(query: Union[str, EquityQuery, FundQuery],
# Switch to Yahoo's predefined endpoint
if size is not None:
print_once("YF deprecation warning: 'size' argument is deprecated for predefined screens, set 'count' instead.")
warnings.warn("Screen 'size' argument is deprecated for predefined screens, set 'count' instead.", DeprecationWarning, stacklevel=2)
count = size
size = None
fields['count'] = fields['size']
@@ -170,7 +170,7 @@ def screen(query: Union[str, EquityQuery, FundQuery],
resp = _data.get(url=_PREDEFINED_URL_, params=params_dict)
try:
resp.raise_for_status()
except requests.exceptions.HTTPError:
except curl_cffi.requests.exceptions.HTTPError:
if query not in PREDEFINED_SCREENER_QUERIES:
print(f"yfinance.screen: '{query}' is probably not a predefined query.")
raise

View File

@@ -20,6 +20,7 @@
#
import json as _json
import warnings
from . import utils
from .const import _BASE_URL_, _SENTINEL_
@@ -52,7 +53,7 @@ class Search:
self._data = YfData(session=self.session)
if proxy is not _SENTINEL_:
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
self.query = query

View File

@@ -22,17 +22,21 @@
from __future__ import print_function
from collections import namedtuple as _namedtuple
from .scrapers.funds import FundsData
import warnings
import pandas as _pd
from .base import TickerBase
from .const import _BASE_URL_, _SENTINEL_
from .scrapers.funds import FundsData
class Ticker(TickerBase):
def __init__(self, ticker, session=None, proxy=_SENTINEL_):
super(Ticker, self).__init__(ticker, session=session, proxy=proxy)
if proxy is not _SENTINEL_:
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
super(Ticker, self).__init__(ticker, session=session)
self._expirations = {}
self._underlying = {}

View File

@@ -21,9 +21,10 @@
from __future__ import print_function
import warnings
from . import Ticker, multi
from .live import WebSocket
from .utils import print_once
from .data import YfData
from .const import _SENTINEL_
@@ -56,8 +57,9 @@ class Tickers:
timeout=10, **kwargs):
if proxy is not _SENTINEL_:
print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
proxy = _SENTINEL_
return self.download(
period, interval,
@@ -75,8 +77,9 @@ class Tickers:
timeout=10, **kwargs):
if proxy is not _SENTINEL_:
print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
warnings.warn("Set proxy via new config function: yf.set_config(proxy=proxy)", DeprecationWarning, stacklevel=2)
self._data._set_proxy(proxy)
proxy = _SENTINEL_
data = multi.download(self.symbols,
start=start, end=end,

View File

@@ -27,7 +27,7 @@ import re
import re as _re
import sys as _sys
import threading
from functools import lru_cache, wraps
from functools import wraps
from inspect import getmembers
from types import FunctionType
from typing import List, Optional
@@ -50,13 +50,6 @@ def attributes(obj):
if name[0] != '_' and name not in disallowed_names and hasattr(obj, name)}
@lru_cache(maxsize=20)
def print_once(msg):
# 'warnings' module suppression of repeat messages does not work.
# This function replicates correct behaviour
print(msg)
# Logging
# Note: most of this logic is adding indentation with function depth,
# so that DEBUG log is readable.
@@ -181,18 +174,14 @@ def is_isin(string):
return bool(_re.match("^([A-Z]{2})([A-Z0-9]{9})([0-9])$", string))
def get_all_by_isin(isin, proxy=const._SENTINEL_, session=None):
def get_all_by_isin(isin):
if not (is_isin(isin)):
raise ValueError("Invalid ISIN number")
if proxy is not const._SENTINEL_:
print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
proxy = None
# Deferred this to prevent circular imports
from .search import Search
search = Search(query=isin, max_results=1, session=session, proxy=proxy)
search = Search(query=isin, max_results=1)
# Extract the first quote and news
ticker = search.quotes[0] if search.quotes else {}
@@ -210,18 +199,18 @@ def get_all_by_isin(isin, proxy=const._SENTINEL_, session=None):
}
def get_ticker_by_isin(isin, proxy=const._SENTINEL_, session=None):
data = get_all_by_isin(isin, proxy, session)
def get_ticker_by_isin(isin):
data = get_all_by_isin(isin)
return data.get('ticker', {}).get('symbol', '')
def get_info_by_isin(isin, proxy=const._SENTINEL_, session=None):
data = get_all_by_isin(isin, proxy, session)
def get_info_by_isin(isin):
data = get_all_by_isin(isin)
return data.get('ticker', {})
def get_news_by_isin(isin, proxy=const._SENTINEL_, session=None):
data = get_all_by_isin(isin, proxy, session)
def get_news_by_isin(isin):
data = get_all_by_isin(isin)
return data.get('news', {})

View File

@@ -1 +1 @@
version = "0.2.61"
version = "0.2.63"