From bd3489ea71da5cdc6fa8ae2820dc7023cdad16d0 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 26 Aug 2023 10:38:31 +0200 Subject: [PATCH 01/38] Handle coinmarketcap return null for some prices. --- src/pricehist/sources/coinmarketcap.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index 313c493..7da1a25 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -57,7 +57,8 @@ class CoinMarketCap(BaseSource): for item in data.get("quotes", []): d = item["time_open"][0:10] amount = self._amount(next(iter(item["quote"].values())), series.type) - prices.append(Price(d, amount)) + if amount is not None: + prices.append(Price(d, amount)) output_base, output_quote = self._output_pair(series.base, series.quote, data) @@ -155,12 +156,14 @@ class CoinMarketCap(BaseSource): return parsed["data"] def _amount(self, data, type): - if type in ["mid"]: + if type in ["mid"] and data["high"] is not None and data["low"] is not None: high = Decimal(str(data["high"])) low = Decimal(str(data["low"])) return sum([high, low]) / 2 - else: + elif type in data and data[type] is not None: return Decimal(str(data[type])) + else: + return None def _output_pair(self, base, quote, data): data_base = data["symbol"] From 786ddd3c8c53dbfdd4f0dde889ffcdd7aaa279f2 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 26 Aug 2023 10:48:49 +0200 Subject: [PATCH 02/38] Update which Alphavantage test is skipped. --- tests/live.sh | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/live.sh b/tests/live.sh index 9224a0d..2fd2b09 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -69,8 +69,11 @@ date,base,quote,amount,source,type 2021-01-07,TSLA,USD,816.04,alphavantage,close 2021-01-08,TSLA,USD,880.02,alphavantage,close END -run_test "$name" "$cmd" "$expected" - +if [[ "$(date --iso-8601)" < "2023-10-01" ]]; then + skip_test "$name" "$cmd" "$expected" +else + run_test "$name" "$cmd" "$expected" +fi name="Alpha Vantage physical currency" cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-04 -e 2021-01-08" @@ -80,7 +83,7 @@ date,base,quote,amount,source,type 2021-01-05,AUD,EUR,0.63086,alphavantage,close 2021-01-06,AUD,EUR,0.63306,alphavantage,close 2021-01-07,AUD,EUR,0.63284,alphavantage,close -2021-01-08,AUD,EUR,0.63530,alphavantage,close +2021-01-08,AUD,EUR,0.63360,alphavantage,close END run_test "$name" "$cmd" "$expected" @@ -94,11 +97,7 @@ date,base,quote,amount,source,type 2021-01-07,BTC,USD,39432.28000000,alphavantage,close 2021-01-08,BTC,USD,40582.81000000,alphavantage,close END -if [[ "$(date --iso-8601)" < "2023-06-15" ]]; then - skip_test "$name" "$cmd" "$expected" -else - run_test "$name" "$cmd" "$expected" -fi +run_test "$name" "$cmd" "$expected" name="Bank of Canada" cmd="pricehist fetch bankofcanada CAD/USD -s 2021-01-04 -e 2021-01-08" @@ -141,11 +140,11 @@ name="CoinMarketCap" cmd="pricehist fetch coinmarketcap BTC/EUR -s 2021-01-04 -e 2021-01-08" read -r -d '' expected < Date: Sat, 26 Aug 2023 10:50:41 +0200 Subject: [PATCH 03/38] Revert "Update Alphavantage source for changes in which endpoint is premium." This reverts commit d6036c9d148b79b3fac39c0ba5c713dd88497c8b. --- src/pricehist/sources/alphavantage.py | 12 +++++++----- tests/live.sh | 10 +++++----- tests/pricehist/sources/test_alphavantage.py | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index 4824282..707ba50 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -51,7 +51,8 @@ class AlphaVantage(BaseSource): "will list all digital and physical currency symbols.\n" "The PAIR for stocks is the stock symbol only. The quote currency " f"will be determined automatically. {self._stock_symbols_message()}\n" - "The price type 'adjclose' is only available for stocks.\n" + "The price type 'adjclose' is only available for stocks, and " + "requires an access key for which premium endpoints are unlocked.\n" "Beware that digital currencies quoted in non-USD currencies may " "be converted from USD data at one recent exchange rate rather " "than using historical rates.\n" @@ -186,9 +187,10 @@ class AlphaVantage(BaseSource): def _stock_data(self, series): output_quote = self._stock_currency(series.base) or "UNKNOWN" - # As of 2022-11-24 TIME_SERIES_DAILY_ADJUSTED is no longer premium, but - # now TIME_SERIES_DAILY is. So, always use TIME_SERIES_DAILY_ADJUSTED. - function = "TIME_SERIES_DAILY_ADJUSTED" + if series.type == "adjclose": + function = "TIME_SERIES_DAILY_ADJUSTED" + else: + function = "TIME_SERIES_DAILY" params = { "function": function, @@ -339,7 +341,7 @@ class AlphaVantage(BaseSource): raise exceptions.RateLimit(data["Note"]) if ( "Information" in data - and "unlock all premium endpoints" in data["Information"] + and "ways to unlock premium" in data["Information"] ): msg = "You were denied access to a premium endpoint." raise exceptions.CredentialsError([self.API_KEY_NAME], self, msg) diff --git a/tests/live.sh b/tests/live.sh index 2fd2b09..78da3c0 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -63,11 +63,11 @@ name="Alpha Vantage stocks" cmd="pricehist fetch alphavantage TSLA -s 2021-01-04 -e 2021-01-08" read -r -d '' expected < Date: Sat, 26 Aug 2023 10:57:06 +0200 Subject: [PATCH 04/38] Don't skip any AlphaVantage tests anymore. All pass. --- tests/live.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/live.sh b/tests/live.sh index 78da3c0..48e1e94 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -69,11 +69,7 @@ date,base,quote,amount,source,type 2021-01-07,TSLA,USD,816.0400,alphavantage,close 2021-01-08,TSLA,USD,880.0200,alphavantage,close END -if [[ "$(date --iso-8601)" < "2023-10-01" ]]; then - skip_test "$name" "$cmd" "$expected" -else - run_test "$name" "$cmd" "$expected" -fi +run_test "$name" "$cmd" "$expected" name="Alpha Vantage physical currency" cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-04 -e 2021-01-08" From 06c2876152135f87dd46850bf4d0108e19e9dccc Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 26 Aug 2023 10:57:40 +0200 Subject: [PATCH 05/38] Make AlphaVantage premium endpoint rejection message check more robust. --- src/pricehist/sources/alphavantage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index 707ba50..270f70f 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -341,7 +341,8 @@ class AlphaVantage(BaseSource): raise exceptions.RateLimit(data["Note"]) if ( "Information" in data - and "ways to unlock premium" in data["Information"] + and "unlock" in data["Information"] + and "premium" in data["Information"] ): msg = "You were denied access to a premium endpoint." raise exceptions.CredentialsError([self.API_KEY_NAME], self, msg) From 46dfd876eaa542b51ac82da283fd1bb1fccdd9ad Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 26 Aug 2023 11:00:09 +0200 Subject: [PATCH 06/38] Version 1.4.6. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 35a776c..d42be19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.5" +version = "1.4.6" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index 56dadec..bde0031 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.5" +__version__ = "1.4.6" From 04936c5cd62fd2858747c15a4612c13e05549791 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 11:40:12 +0200 Subject: [PATCH 07/38] Update lxml dependency. --- poetry.lock | 1210 ++++++++++++++++++++++++++---------------------- pyproject.toml | 2 +- 2 files changed, 654 insertions(+), 558 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9df92df..5aff69d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,32 +1,54 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + [[package]] name = "atomicwrites" version = "1.4.1" description = "Atomic file writes." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] [[package]] name = "attrs" -version = "22.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] [package.dependencies] click = ">=8.0.0" @@ -44,30 +66,124 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -76,417 +192,20 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -toml = ["toml"] - -[[package]] -name = "cssselect" -version = "1.2.0" -description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "curlify" -version = "2.2.1" -description = "Library to convert python requests object to curl command." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -requests = "*" - -[[package]] -name = "distlib" -version = "0.3.6" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "filelock" -version = "3.8.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "flake8" -version = "3.9.2" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "lxml" -version = "4.9.1" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.7)"] - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pathspec" -version = "0.10.2" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "platformdirs" -version = "2.5.4" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycodestyle" -version = "2.7.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "2.3.1" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "6.2.5" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -name = "pytest-mock" -version = "3.10.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pytest = ">=5.0" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "responses" -version = "0.13.4" -description = "A utility library for mocking out the `requests` Python library." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -requests = ">=2.0" -six = "*" -urllib3 = ">=1.25.10" - -[package.extras] -tests = ["coverage (>=3.7.1,<6.0.0)", "flake8", "mypy", "pytest (>=4.6)", "pytest (>=4.6,<5.0)", "pytest-cov", "pytest-localserver", "types-mock", "types-requests", "types-six"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tox" -version = "3.27.1" -description = "tox is a generic virtualenv management and test command line tool" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.13" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.16.7" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "c8dc7901eea7c89dca425872abe275d5a1fc8d6f1a1d39ebf7aa7cd632d7dc44" - -[metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ +files = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, @@ -540,190 +259,567 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] -cssselect = [ + +[package.extras] +toml = ["toml"] + +[[package]] +name = "cssselect" +version = "1.2.0" +description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" +optional = false +python-versions = ">=3.7" +files = [ {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, ] -curlify = [ + +[[package]] +name = "curlify" +version = "2.2.1" +description = "Library to convert python requests object to curl command." +optional = false +python-versions = "*" +files = [ {file = "curlify-2.2.1.tar.gz", hash = "sha256:0d3f02e7235faf952de8ef45ef469845196d30632d5838bcd5aee217726ddd6d"}, ] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + +[package.dependencies] +requests = "*" + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] -filelock = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] -flake8 = [ + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] -lxml = [ - {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"}, - {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"}, - {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"}, - {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"}, - {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"}, - {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"}, - {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"}, - {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"}, - {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"}, - {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"}, - {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"}, - {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"}, - {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"}, - {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"}, - {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"}, - {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"}, - {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"}, - {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"}, - {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"}, - {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"}, - {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"}, - {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"}, - {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"}, - {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"}, - {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"}, - {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"}, - {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"}, - {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"}, - {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"}, - {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"}, - {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"}, - {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"}, - {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"}, - {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"}, - {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"}, - {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"}, - {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"}, - {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"}, - {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"}, - {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"}, - {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"}, - {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"}, - {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"}, - {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"}, - {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"}, - {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"}, - {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"}, - {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"}, - {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"}, - {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"}, - {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"}, - {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"}, - {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"}, - {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"}, - {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"}, - {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"}, - {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"}, - {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"}, - {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"}, - {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"}, - {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"}, - {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"}, - {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"}, - {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"}, - {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"}, - {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"}, - {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"}, - {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"}, - {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"}, - {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"}, + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "lxml" +version = "5.2.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, + {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, + {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, + {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, + {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, + {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, + {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, + {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, + {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, + {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, + {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, + {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, + {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, + {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, + {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, + {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, + {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, + {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, + {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, ] -mccabe = [ + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.10)"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = "*" +files = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -platformdirs = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] -py = [ + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -pycodestyle = [ + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] -pyflakes = [ + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pytest = [ + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] -pytest-mock = [ - {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, - {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, ] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] -responses = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "responses" +version = "0.13.4" +description = "A utility library for mocking out the `requests` Python library." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "responses-0.13.4-py2.py3-none-any.whl", hash = "sha256:d8d0f655710c46fd3513b9202a7f0dcedd02ca0f8cf4976f27fa8ab5b81e656d"}, {file = "responses-0.13.4.tar.gz", hash = "sha256:9476775d856d3c24ae660bbebe29fb6d789d4ad16acd723efbfb6ee20990b899"}, ] -six = [ + +[package.dependencies] +requests = ">=2.0" +six = "*" +urllib3 = ">=1.25.10" + +[package.extras] +tests = ["coverage (>=3.7.1,<6.0.0)", "flake8", "mypy", "pytest (>=4.6)", "pytest (>=4.6,<5.0)", "pytest-cov", "pytest-localserver", "types-mock", "types-requests", "types-six"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -toml = [ + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tomli = [ + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -tox = [ - {file = "tox-3.27.1-py2.py3-none-any.whl", hash = "sha256:f52ca66eae115fcfef0e77ef81fd107133d295c97c52df337adedb8dfac6ab84"}, - {file = "tox-3.27.1.tar.gz", hash = "sha256:b2a920e35a668cc06942ffd1cf3a4fb221a4d909ca72191fb6d84b0b18a7be04"}, + +[[package]] +name = "tox" +version = "3.28.0" +description = "tox is a generic virtualenv management and test command line tool" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, + {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, ] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] -virtualenv = [ - {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, - {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, ] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "98395da76c16ddf7b7c3c9ae128f47b574e7d6775ddbc7f9b61ceea31492ab92" diff --git a/pyproject.toml b/pyproject.toml index d42be19..0840342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ include = [ [tool.poetry.dependencies] python = "^3.8" requests = "^2.25.1" -lxml = "^4.6.2" +lxml = "^5.1.0" cssselect = "^1.1.0" curlify = "^2.2.1" From 6519cf28454a5af6097acfdb75f95cf7a22c3ce7 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 11:40:38 +0200 Subject: [PATCH 08/38] Update datetime formatting. --- src/pricehist/outputs/gnucashsql.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pricehist/outputs/gnucashsql.py b/src/pricehist/outputs/gnucashsql.py index 44841c1..8ccbff7 100644 --- a/src/pricehist/outputs/gnucashsql.py +++ b/src/pricehist/outputs/gnucashsql.py @@ -40,7 +40,7 @@ Classes: import hashlib import logging -from datetime import datetime +from datetime import datetime, timezone from decimal import Decimal from importlib.resources import files @@ -125,7 +125,7 @@ class GnuCashSQL(BaseOutput): .read_text() .format( version=__version__, - timestamp=datetime.utcnow().isoformat() + "Z", + timestamp=datetime.now(timezone.utc).isoformat()[:-6] + "Z", base=self._sql_str(base), quote=self._sql_str(quote), values_comment=values_comment, From 96d3e44738d194e0c85a94b88a11b4f324c7eb99 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 14:56:28 +0200 Subject: [PATCH 09/38] Update python to ^3.8.1 and flake8 to ^7.1.0. --- poetry.lock | 42 +++++++++++++++++++++--------------------- pyproject.toml | 4 ++-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5aff69d..9d3681b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -316,19 +316,19 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" -version = "3.9.2" +version = "7.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.8.1" files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, + {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, + {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, ] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "idna" @@ -526,13 +526,13 @@ source = ["Cython (>=3.0.10)"] [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] @@ -612,24 +612,24 @@ files = [ [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.12.0" description = "Python style guide checker" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, ] [[package]] name = "pyflakes" -version = "2.3.1" +version = "3.2.0" description = "passive checker of Python programs" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] @@ -821,5 +821,5 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "98395da76c16ddf7b7c3c9ae128f47b574e7d6775ddbc7f9b61ceea31492ab92" +python-versions = "^3.8.1" +content-hash = "de3fe2ed9cb9ec204b3e1f94150f17b0a27b3c13a722164e5118512691cf248d" diff --git a/pyproject.toml b/pyproject.toml index 0840342..a4d2752 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.8" +python = "^3.8.1" requests = "^2.25.1" lxml = "^5.1.0" cssselect = "^1.1.0" @@ -23,7 +23,7 @@ curlify = "^2.2.1" [tool.poetry.dev-dependencies] pytest = "^6.2.2" black = "^22.10.0" -flake8 = "^3.9.1" +flake8 = "^7.1.0" isort = "^5.8.0" responses = "^0.13.3" coverage = "^5.5" From 733c849286df1379782429aed7826a3749a98712 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 14:59:02 +0200 Subject: [PATCH 10/38] Follow flake8 advice. --- src/pricehist/sources/alphavantage.py | 10 +++++----- src/pricehist/sources/coinmarketcap.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index 270f70f..ea80621 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -175,9 +175,9 @@ class AlphaVantage(BaseSource): expected_keys = ["1. symbol", "2. name", "3. type", "4. region", "8. currency"] if ( - type(data) != dict + type(data) is not dict or "bestMatches" not in data - or type(data["bestMatches"]) != list + or type(data["bestMatches"]) is not list or not all(k in m for k in expected_keys for m in data["bestMatches"]) ): raise exceptions.ResponseParsingError("Unexpected content.") @@ -267,7 +267,7 @@ class AlphaVantage(BaseSource): self._raise_for_generic_errors(data) - if type(data) != dict or "Time Series FX (Daily)" not in data: + if type(data) is not dict or "Time Series FX (Daily)" not in data: raise exceptions.ResponseParsingError("Unexpected content.") normalized_data = { @@ -308,7 +308,7 @@ class AlphaVantage(BaseSource): self._raise_for_generic_errors(data) - if type(data) != dict or "Time Series (Digital Currency Daily)" not in data: + if type(data) is not dict or "Time Series (Digital Currency Daily)" not in data: raise exceptions.ResponseParsingError("Unexpected content.") normalized_data = { @@ -336,7 +336,7 @@ class AlphaVantage(BaseSource): return key def _raise_for_generic_errors(self, data): - if type(data) == dict: + if type(data) is dict: if "Note" in data and "call frequency" in data["Note"]: raise exceptions.RateLimit(data["Note"]) if ( diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index 7da1a25..8cdf03a 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -144,7 +144,7 @@ class CoinMarketCap(BaseSource): except Exception as e: raise exceptions.ResponseParsingError(str(e)) from e - if type(parsed) != dict or "data" not in parsed: + if type(parsed) is not dict or "data" not in parsed: raise exceptions.ResponseParsingError("Unexpected content.") elif len(parsed["data"]) == 0: @@ -208,7 +208,7 @@ class CoinMarketCap(BaseSource): except Exception as e: raise exceptions.ResponseParsingError(str(e)) from e - if type(parsed) != dict or "data" not in parsed: + if type(parsed) is not dict or "data" not in parsed: raise exceptions.ResponseParsingError("Unexpected content.") elif len(parsed["data"]) == 0: From 0b377a8d65f357c040b24dabf1cad8a6f2782b11 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 15:29:51 +0200 Subject: [PATCH 11/38] Fix description of data taht doesn't overlap the requested range. --- src/pricehist/fetch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pricehist/fetch.py b/src/pricehist/fetch.py index db051de..a7faba7 100644 --- a/src/pricehist/fetch.py +++ b/src/pricehist/fetch.py @@ -80,5 +80,8 @@ def _cov_description( f"and ends {end_uncovered} day{s(end_uncovered)} earlier " f"than requested" ) - else: + elif start_uncovered == 0 and end_uncovered == 0: return "as requested" + else: + return "which doesn't match the request" + From f4aee183603b92440177e83ad1dba2c196c460a4 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 15:30:45 +0200 Subject: [PATCH 12/38] Update parsing of Alphavantage digital currency response data. --- src/pricehist/sources/alphavantage.py | 8 +- .../test_alphavantage/btc-aud-partial.json | 105 ++++++------------ 2 files changed, 39 insertions(+), 74 deletions(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index ea80621..698add7 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -313,10 +313,10 @@ class AlphaVantage(BaseSource): normalized_data = { day: { - "open": entries[f"1a. open ({series.quote})"], - "high": entries[f"2a. high ({series.quote})"], - "low": entries[f"3a. low ({series.quote})"], - "close": entries[f"4a. close ({series.quote})"], + "open": entries[f"1. open"], + "high": entries[f"2. high"], + "low": entries[f"3. low"], + "close": entries[f"4. close"], } for day, entries in reversed( data["Time Series (Digital Currency Daily)"].items() diff --git a/tests/pricehist/sources/test_alphavantage/btc-aud-partial.json b/tests/pricehist/sources/test_alphavantage/btc-aud-partial.json index cd6412d..737658f 100644 --- a/tests/pricehist/sources/test_alphavantage/btc-aud-partial.json +++ b/tests/pricehist/sources/test_alphavantage/btc-aud-partial.json @@ -10,88 +10,53 @@ }, "Time Series (Digital Currency Daily)": { "2021-01-09": { - "1a. open (AUD)": "55074.06950240", - "1b. open (USD)": "40586.96000000", - "2a. high (AUD)": "56150.17720000", - "2b. high (USD)": "41380.00000000", - "3a. low (AUD)": "52540.71680000", - "3b. low (USD)": "38720.00000000", - "4a. close (AUD)": "54397.30924680", - "4b. close (USD)": "40088.22000000", - "5. volume": "75785.97967500", - "6. market cap (USD)": "75785.97967500" + "1. open": "55074.06950240", + "2. high": "56150.17720000", + "3. low": "52540.71680000", + "4. close": "54397.30924680", + "5. volume": "75785.97967500" }, "2021-01-08": { - "1a. open (AUD)": "53507.50941120", - "1b. open (USD)": "39432.48000000", - "2a. high (AUD)": "56923.63300000", - "2b. high (USD)": "41950.00000000", - "3a. low (AUD)": "49528.31000000", - "3b. low (USD)": "36500.00000000", - "4a. close (AUD)": "55068.43820140", - "4b. close (USD)": "40582.81000000", - "5. volume": "139789.95749900", - "6. market cap (USD)": "139789.95749900" + "1. open": "53507.50941120", + "2. high": "56923.63300000", + "3. low": "49528.31000000", + "4. close": "55068.43820140", + "5. volume": "139789.95749900" }, "2021-01-07": { - "1a. open (AUD)": "49893.81535840", - "1b. open (USD)": "36769.36000000", - "2a. high (AUD)": "54772.88310000", - "2b. high (USD)": "40365.00000000", - "3a. low (AUD)": "49256.92200000", - "3b. low (USD)": "36300.00000000", - "4a. close (AUD)": "53507.23802320", - "4b. close (USD)": "39432.28000000", - "5. volume": "132825.70043700", - "6. market cap (USD)": "132825.70043700" + "1. open": "49893.81535840", + "2. high": "54772.88310000", + "3. low": "49256.92200000", + "4. close": "53507.23802320", + "5. volume": "132825.70043700" }, "2021-01-06": { - "1a. open (AUD)": "46067.47523820", - "1b. open (USD)": "33949.53000000", - "2a. high (AUD)": "50124.29161740", - "2b. high (USD)": "36939.21000000", - "3a. low (AUD)": "45169.81872000", - "3b. low (USD)": "33288.00000000", - "4a. close (AUD)": "49893.81535840", - "4b. close (USD)": "36769.36000000", - "5. volume": "127139.20131000", - "6. market cap (USD)": "127139.20131000" + "1. open": "46067.47523820", + "2. high": "50124.29161740", + "3. low": "45169.81872000", + "4. close": "49893.81535840", + "5. volume": "127139.20131000" }, "2021-01-05": { - "1a. open (AUD)": "43408.17136500", - "1b. open (USD)": "31989.75000000", - "2a. high (AUD)": "46624.45840000", - "2b. high (USD)": "34360.00000000", - "3a. low (AUD)": "40572.50600000", - "3b. low (USD)": "29900.00000000", - "4a. close (AUD)": "46067.47523820", - "4b. close (USD)": "33949.53000000", - "5. volume": "116049.99703800", - "6. market cap (USD)": "116049.99703800" + "1. open": "43408.17136500", + "2. high": "46624.45840000", + "3. low": "40572.50600000", + "4. close": "46067.47523820", + "5. volume": "116049.99703800" }, "2021-01-04": { - "1a. open (AUD)": "44779.08784700", - "1b. open (USD)": "33000.05000000", - "2a. high (AUD)": "45593.18400000", - "2b. high (USD)": "33600.00000000", - "3a. low (AUD)": "38170.72220000", - "3b. low (USD)": "28130.00000000", - "4a. close (AUD)": "43406.76014740", - "4b. close (USD)": "31988.71000000", - "5. volume": "140899.88569000", - "6. market cap (USD)": "140899.88569000" + "1. open": "44779.08784700", + "2. high": "45593.18400000", + "3. low": "38170.72220000", + "4. close": "43406.76014740", + "5. volume": "140899.88569000" }, "2021-01-03": { - "1a. open (AUD)": "43661.51206300", - "1b. open (USD)": "32176.45000000", - "2a. high (AUD)": "47191.80858340", - "2b. high (USD)": "34778.11000000", - "3a. low (AUD)": "43371.85965060", - "3b. low (USD)": "31962.99000000", - "4a. close (AUD)": "44779.08784700", - "4b. close (USD)": "33000.05000000", - "5. volume": "120957.56675000", - "6. market cap (USD)": "120957.56675000" + "1. open": "43661.51206300", + "2. high": "47191.80858340", + "3. low": "43371.85965060", + "4. close": "44779.08784700", + "5. volume": "120957.56675000" } } } From 1f01c54c4dfdacc31c912565feba500d196b3a96 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 15:31:37 +0200 Subject: [PATCH 13/38] Update alphavantage physical and digital currency live test cases. --- tests/live.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/live.sh b/tests/live.sh index 48e1e94..93bcb4e 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -72,26 +72,26 @@ END run_test "$name" "$cmd" "$expected" name="Alpha Vantage physical currency" -cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-04 -e 2021-01-08" +cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-10 -e 2021-01-14" read -r -d '' expected < Date: Thu, 11 Jul 2024 15:32:10 +0200 Subject: [PATCH 14/38] Skip live tests for sources with known issues that need more work. --- tests/live.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/live.sh b/tests/live.sh index 93bcb4e..a25b93d 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -130,7 +130,7 @@ date,base,quote,amount,source,type 2021-01-07,BTC,EUR,32183.1594,coindesk,close 2021-01-08,BTC,EUR,33238.5724,coindesk,close END -run_test "$name" "$cmd" "$expected" +skip_test "$name" "$cmd" "$expected" name="CoinMarketCap" cmd="pricehist fetch coinmarketcap BTC/EUR -s 2021-01-04 -e 2021-01-08" @@ -142,7 +142,7 @@ date,base,quote,amount,source,type 2021-01-07,BTC,EUR,31200.6391028267445,coinmarketcap,mid 2021-01-08,BTC,EUR,32154.244768031175,coinmarketcap,mid END -run_test "$name" "$cmd" "$expected" +skip_test "$name" "$cmd" "$expected" name="European Central Bank" cmd="pricehist fetch ecb EUR/JPY -s 2021-01-04 -e 2021-01-08" From a12f3d3899a2916f3ef8e080fd12560543fda0e3 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 15:34:32 +0200 Subject: [PATCH 15/38] Version 1.4.7. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a4d2752..b6b53d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.6" +version = "1.4.7" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index bde0031..ac329c9 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.6" +__version__ = "1.4.7" From 47544a11b6a492610f300f0df733d09c7bab0f91 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 15:52:35 +0200 Subject: [PATCH 16/38] Minor formatting. --- src/pricehist/fetch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pricehist/fetch.py b/src/pricehist/fetch.py index a7faba7..aba61f2 100644 --- a/src/pricehist/fetch.py +++ b/src/pricehist/fetch.py @@ -84,4 +84,3 @@ def _cov_description( return "as requested" else: return "which doesn't match the request" - From b8c4554298f12056344ef347b5329801493712a1 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Thu, 11 Jul 2024 16:25:30 +0200 Subject: [PATCH 17/38] Fix flake8 warning. --- src/pricehist/sources/alphavantage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index 698add7..e60f0f6 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -313,10 +313,10 @@ class AlphaVantage(BaseSource): normalized_data = { day: { - "open": entries[f"1. open"], - "high": entries[f"2. high"], - "low": entries[f"3. low"], - "close": entries[f"4. close"], + "open": entries["1. open"], + "high": entries["2. high"], + "low": entries["3. low"], + "close": entries["4. close"], } for day, entries in reversed( data["Time Series (Digital Currency Daily)"].items() From 8921653154b43e71e9647b6fcbccaf13aff9c3db Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Tue, 23 Jul 2024 22:08:22 +0200 Subject: [PATCH 18/38] Fix coinmarketcap: first pass. --- src/pricehist/sources/coinmarketcap.py | 144 ++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 17 deletions(-) diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index 8cdf03a..f7e89ab 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -2,6 +2,7 @@ import dataclasses import json from datetime import datetime, timezone from decimal import Decimal +from functools import lru_cache import requests @@ -55,8 +56,8 @@ class CoinMarketCap(BaseSource): prices = [] for item in data.get("quotes", []): - d = item["time_open"][0:10] - amount = self._amount(next(iter(item["quote"].values())), series.type) + d = item["timeOpen"][0:10] + amount = self._amount(item["quote"], series.type) if amount is not None: prices.append(Price(d, amount)) @@ -67,21 +68,21 @@ class CoinMarketCap(BaseSource): ) def _data(self, series): - url = "https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical" + url = "https://api.coinmarketcap.com/data-api/v3.1/cryptocurrency/historical" params = {} if series.base.startswith("ID="): params["id"] = series.base[3:] else: - params["symbol"] = series.base + params["id"] = self._id_from_symbol(series.base, series) if series.quote.startswith("ID="): - params["convert_id"] = series.quote[3:] + params["convertId"] = series.quote[3:] else: - params["convert"] = series.quote + params["convertId"] = self._id_from_symbol(series.quote, series) - params["time_start"] = int( + params["timeStart"] = int( int( datetime.strptime(series.start, "%Y-%m-%d") .replace(tzinfo=timezone.utc) @@ -90,12 +91,14 @@ class CoinMarketCap(BaseSource): - 24 * 60 * 60 # Start one period earlier since the start is exclusive. ) - params["time_end"] = int( + params["timeEnd"] = int( datetime.strptime(series.end, "%Y-%m-%d") .replace(tzinfo=timezone.utc) .timestamp() ) # Don't round up since it's inclusive of the period covering the end time. + params["interval"] = "daily" + try: response = self.log_curl(requests.get(url, params=params)) except Exception as e: @@ -114,11 +117,6 @@ class CoinMarketCap(BaseSource): series.base, series.quote, self, "Bad quote ID." ) - elif code == 400 and 'Invalid value for \\"convert\\"' in text: - raise exceptions.InvalidPair( - series.base, series.quote, self, "Bad quote symbol." - ) - elif code == 400 and "must be older than" in text: if series.start <= series.end: raise exceptions.BadResponse("The start date must be in the past.") @@ -182,15 +180,127 @@ class CoinMarketCap(BaseSource): return (output_base, output_quote) + def _id_from_symbol(self, symbol, series): + for i in self._symbol_data(): + if i["symbol"] == symbol: + return i["id"] + raise exceptions.InvalidPair( + series.base, series.quote, self, f"Invalid symbol '{symbol}'." + ) + + @lru_cache(maxsize=1) def _symbol_data(self): - base_url = "https://web-api.coinmarketcap.com/v1/" - fiat_url = f"{base_url}fiat/map?include_metals=true" + + base_url = "https://api.coinmarketcap.com/data-api/v1/" crypto_url = f"{base_url}cryptocurrency/map?sort=cmc_rank" - fiat = self._get_json_data(fiat_url) crypto = self._get_json_data(crypto_url) - return crypto + fiat + # fmt: off + fiat = [ + {"id": 2781, "symbol": "USD", "name": "United States Dollar"}, + {"id": 3526, "symbol": "ALL", "name": "Albanian Lek"}, + {"id": 3537, "symbol": "DZD", "name": "Algerian Dinar"}, + {"id": 2821, "symbol": "ARS", "name": "Argentine Peso"}, + {"id": 3527, "symbol": "AMD", "name": "Armenian Dram"}, + {"id": 2782, "symbol": "AUD", "name": "Australian Dollar"}, + {"id": 3528, "symbol": "AZN", "name": "Azerbaijani Manat"}, + {"id": 3531, "symbol": "BHD", "name": "Bahraini Dinar"}, + {"id": 3530, "symbol": "BDT", "name": "Bangladeshi Taka"}, + {"id": 3533, "symbol": "BYN", "name": "Belarusian Ruble"}, + {"id": 3532, "symbol": "BMD", "name": "Bermudan Dollar"}, + {"id": 2832, "symbol": "BOB", "name": "Bolivian Boliviano"}, + {"id": 3529, "symbol": "BAM", "name": "Bosnia-Herzegovina Convertible Mark"}, # noqa: E501 + {"id": 2783, "symbol": "BRL", "name": "Brazilian Real"}, + {"id": 2814, "symbol": "BGN", "name": "Bulgarian Lev"}, + {"id": 3549, "symbol": "KHR", "name": "Cambodian Riel"}, + {"id": 2784, "symbol": "CAD", "name": "Canadian Dollar"}, + {"id": 2786, "symbol": "CLP", "name": "Chilean Peso"}, + {"id": 2787, "symbol": "CNY", "name": "Chinese Yuan"}, + {"id": 2820, "symbol": "COP", "name": "Colombian Peso"}, + {"id": 3534, "symbol": "CRC", "name": "Costa Rican Colón"}, + {"id": 2815, "symbol": "HRK", "name": "Croatian Kuna"}, + {"id": 3535, "symbol": "CUP", "name": "Cuban Peso"}, + {"id": 2788, "symbol": "CZK", "name": "Czech Koruna"}, + {"id": 2789, "symbol": "DKK", "name": "Danish Krone"}, + {"id": 3536, "symbol": "DOP", "name": "Dominican Peso"}, + {"id": 3538, "symbol": "EGP", "name": "Egyptian Pound"}, + {"id": 2790, "symbol": "EUR", "name": "Euro"}, + {"id": 3539, "symbol": "GEL", "name": "Georgian Lari"}, + {"id": 3540, "symbol": "GHS", "name": "Ghanaian Cedi"}, + {"id": 3541, "symbol": "GTQ", "name": "Guatemalan Quetzal"}, + {"id": 3542, "symbol": "HNL", "name": "Honduran Lempira"}, + {"id": 2792, "symbol": "HKD", "name": "Hong Kong Dollar"}, + {"id": 2793, "symbol": "HUF", "name": "Hungarian Forint"}, + {"id": 2818, "symbol": "ISK", "name": "Icelandic Króna"}, + {"id": 2796, "symbol": "INR", "name": "Indian Rupee"}, + {"id": 2794, "symbol": "IDR", "name": "Indonesian Rupiah"}, + {"id": 3544, "symbol": "IRR", "name": "Iranian Rial"}, + {"id": 3543, "symbol": "IQD", "name": "Iraqi Dinar"}, + {"id": 2795, "symbol": "ILS", "name": "Israeli New Shekel"}, + {"id": 3545, "symbol": "JMD", "name": "Jamaican Dollar"}, + {"id": 2797, "symbol": "JPY", "name": "Japanese Yen"}, + {"id": 3546, "symbol": "JOD", "name": "Jordanian Dinar"}, + {"id": 3551, "symbol": "KZT", "name": "Kazakhstani Tenge"}, + {"id": 3547, "symbol": "KES", "name": "Kenyan Shilling"}, + {"id": 3550, "symbol": "KWD", "name": "Kuwaiti Dinar"}, + {"id": 3548, "symbol": "KGS", "name": "Kyrgystani Som"}, + {"id": 3552, "symbol": "LBP", "name": "Lebanese Pound"}, + {"id": 3556, "symbol": "MKD", "name": "Macedonian Denar"}, + {"id": 2800, "symbol": "MYR", "name": "Malaysian Ringgit"}, + {"id": 2816, "symbol": "MUR", "name": "Mauritian Rupee"}, + {"id": 2799, "symbol": "MXN", "name": "Mexican Peso"}, + {"id": 3555, "symbol": "MDL", "name": "Moldovan Leu"}, + {"id": 3558, "symbol": "MNT", "name": "Mongolian Tugrik"}, + {"id": 3554, "symbol": "MAD", "name": "Moroccan Dirham"}, + {"id": 3557, "symbol": "MMK", "name": "Myanma Kyat"}, + {"id": 3559, "symbol": "NAD", "name": "Namibian Dollar"}, + {"id": 3561, "symbol": "NPR", "name": "Nepalese Rupee"}, + {"id": 2811, "symbol": "TWD", "name": "New Taiwan Dollar"}, + {"id": 2802, "symbol": "NZD", "name": "New Zealand Dollar"}, + {"id": 3560, "symbol": "NIO", "name": "Nicaraguan Córdoba"}, + {"id": 2819, "symbol": "NGN", "name": "Nigerian Naira"}, + {"id": 2801, "symbol": "NOK", "name": "Norwegian Krone"}, + {"id": 3562, "symbol": "OMR", "name": "Omani Rial"}, + {"id": 2804, "symbol": "PKR", "name": "Pakistani Rupee"}, + {"id": 3563, "symbol": "PAB", "name": "Panamanian Balboa"}, + {"id": 2822, "symbol": "PEN", "name": "Peruvian Sol"}, + {"id": 2803, "symbol": "PHP", "name": "Philippine Peso"}, + {"id": 2805, "symbol": "PLN", "name": "Polish Złoty"}, + {"id": 2791, "symbol": "GBP", "name": "Pound Sterling"}, + {"id": 3564, "symbol": "QAR", "name": "Qatari Rial"}, + {"id": 2817, "symbol": "RON", "name": "Romanian Leu"}, + {"id": 2806, "symbol": "RUB", "name": "Russian Ruble"}, + {"id": 3566, "symbol": "SAR", "name": "Saudi Riyal"}, + {"id": 3565, "symbol": "RSD", "name": "Serbian Dinar"}, + {"id": 2808, "symbol": "SGD", "name": "Singapore Dollar"}, + {"id": 2812, "symbol": "ZAR", "name": "South African Rand"}, + {"id": 2798, "symbol": "KRW", "name": "South Korean Won"}, + {"id": 3567, "symbol": "SSP", "name": "South Sudanese Pound"}, + {"id": 3573, "symbol": "VES", "name": "Sovereign Bolivar"}, + {"id": 3553, "symbol": "LKR", "name": "Sri Lankan Rupee"}, + {"id": 2807, "symbol": "SEK", "name": "Swedish Krona"}, + {"id": 2785, "symbol": "CHF", "name": "Swiss Franc"}, + {"id": 2809, "symbol": "THB", "name": "Thai Baht"}, + {"id": 3569, "symbol": "TTD", "name": "Trinidad and Tobago Dollar"}, + {"id": 3568, "symbol": "TND", "name": "Tunisian Dinar"}, + {"id": 2810, "symbol": "TRY", "name": "Turkish Lira"}, + {"id": 3570, "symbol": "UGX", "name": "Ugandan Shilling"}, + {"id": 2824, "symbol": "UAH", "name": "Ukrainian Hryvnia"}, + {"id": 2813, "symbol": "AED", "name": "United Arab Emirates Dirham"}, + {"id": 3571, "symbol": "UYU", "name": "Uruguayan Peso"}, + {"id": 3572, "symbol": "UZS", "name": "Uzbekistan Som"}, + {"id": 2823, "symbol": "VND", "name": "Vietnamese Dong"}, + ] + metals = [ + {"id": 3575, "symbol": "XAU", "name": "Gold Troy Ounce"}, + {"id": 3574, "symbol": "XAG", "name": "Silver Troy Ounce"}, + {"id": 3577, "symbol": "XPT", "name": "Platinum Ounce"}, + {"id": 3576, "symbol": "XPD", "name": "Palladium Ounce"}, + ] + # fmt: on + + return fiat + metals + crypto def _get_json_data(self, url, params={}): try: From 5a0de59aba80d4858b06b776bdb4ac214e4d0868 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Fri, 2 Aug 2024 09:47:07 +0200 Subject: [PATCH 19/38] coinmarketcap: fix quote output. --- src/pricehist/beanprice/exchangeratehost.py | 4 + src/pricehist/sources/coinmarketcap.py | 7 +- src/pricehist/sources/exchangeratehost.py | 122 ++++++++++++++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 src/pricehist/beanprice/exchangeratehost.py create mode 100644 src/pricehist/sources/exchangeratehost.py diff --git a/src/pricehist/beanprice/exchangeratehost.py b/src/pricehist/beanprice/exchangeratehost.py new file mode 100644 index 0000000..ad6525a --- /dev/null +++ b/src/pricehist/beanprice/exchangeratehost.py @@ -0,0 +1,4 @@ +from pricehist import beanprice +from pricehist.sources.exchangeratehost import ExchangeRateHost + +Source = beanprice.source(ExchangeRateHost()) diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index f7e89ab..275df76 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -166,17 +166,18 @@ class CoinMarketCap(BaseSource): def _output_pair(self, base, quote, data): data_base = data["symbol"] + symbols = {i["id"]: (i["symbol"] or i["code"]) for i in self._symbol_data()} + data_quote = None if len(data["quotes"]) > 0: - data_quote = next(iter(data["quotes"][0]["quote"].keys())) + data_quote = symbols[int(data["quotes"][0]["quote"]["name"])] lookup_quote = None if quote.startswith("ID="): - symbols = {i["id"]: (i["symbol"] or i["code"]) for i in self._symbol_data()} lookup_quote = symbols[int(quote[3:])] output_base = data_base - output_quote = lookup_quote or data_quote or quote + output_quote = data_quote or lookup_quote or quote return (output_base, output_quote) diff --git a/src/pricehist/sources/exchangeratehost.py b/src/pricehist/sources/exchangeratehost.py new file mode 100644 index 0000000..76c412a --- /dev/null +++ b/src/pricehist/sources/exchangeratehost.py @@ -0,0 +1,122 @@ +import dataclasses +import json +from decimal import Decimal + +import requests + +from pricehist import exceptions +from pricehist.price import Price + +from .basesource import BaseSource + + +class ExchangeRateHost(BaseSource): + def id(self): + return "exchangeratehost" + + def name(self): + return "exchangerate.host Exchange rates API" + + def description(self): + return ( + "Exchange rates API is a simple and lightweight free service for " + "current and historical foreign exchange rates & crypto exchange " + "rates." + ) + + def source_url(self): + return "https://exchangerate.host/" + + def start(self): + return "1999-01-01" + + def types(self): + return ["close"] + + def notes(self): + return "" + + def symbols(self): + url = "https://api.coindesk.com/v1/bpi/supported-currencies.json" + + try: + response = self.log_curl(requests.get(url)) + except Exception as e: + raise exceptions.RequestError(str(e)) from e + + try: + response.raise_for_status() + except Exception as e: + raise exceptions.BadResponse(str(e)) from e + + try: + data = json.loads(response.content) + relevant = [i for i in data if i["currency"] not in ["BTC", "XBT"]] + results = [ + (f"BTC/{i['currency']}", f"Bitcoin against {i['country']}") + for i in sorted(relevant, key=lambda i: i["currency"]) + ] + except Exception as e: + raise exceptions.ResponseParsingError(str(e)) from e + + if not results: + raise exceptions.ResponseParsingError("Expected data not found") + else: + return results + + def fetch(self, series): + if series.base != "BTC" or series.quote in ["BTC", "XBT"]: + # BTC is the only valid base. + # BTC as the quote will return BTC/USD, which we don't want. + # XBT as the quote will fail with HTTP status 500. + raise exceptions.InvalidPair(series.base, series.quote, self) + + data = self._data(series) + + prices = [] + for (d, v) in data.get("bpi", {}).items(): + prices.append(Price(d, Decimal(str(v)))) + + return dataclasses.replace(series, prices=prices) + + def _data(self, series): + url = "https://api.coindesk.com/v1/bpi/historical/close.json" + params = { + "currency": series.quote, + "start": series.start, + "end": series.end, + } + + try: + response = self.log_curl(requests.get(url, params=params)) + except Exception as e: + raise exceptions.RequestError(str(e)) from e + + code = response.status_code + text = response.text + if code == 404 and "currency was not found" in text: + raise exceptions.InvalidPair(series.base, series.quote, self) + elif code == 404 and "only covers data from" in text: + raise exceptions.BadResponse(text) + elif code == 404 and "end date is before" in text and series.end < series.start: + raise exceptions.BadResponse("End date is before start date.") + elif code == 404 and "end date is before" in text: + raise exceptions.BadResponse("The start date must be in the past.") + elif code == 500 and "No results returned from database" in text: + raise exceptions.BadResponse( + "No results returned from database. This can happen when data " + "for a valid quote currency (e.g. CUP) doesn't go all the way " + "back to the start date, and potentially for other reasons." + ) + else: + try: + response.raise_for_status() + except Exception as e: + raise exceptions.BadResponse(str(e)) from e + + try: + result = json.loads(response.content) + except Exception as e: + raise exceptions.ResponseParsingError(str(e)) from e + + return result From 5fdf16edb7dcfb315bc2f68b4a8d51564f80f816 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 12:33:29 +0200 Subject: [PATCH 20/38] Update pytest. --- poetry.lock | 74 ++++++++++++++++---------------------------------- pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 52 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9d3681b..4016d9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,34 +1,5 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - [[package]] name = "black" version = "22.12.0" @@ -298,6 +269,20 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.15.4" @@ -634,27 +619,25 @@ files = [ [[package]] name = "pytest" -version = "6.2.5" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-mock" @@ -724,17 +707,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -822,4 +794,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "de3fe2ed9cb9ec204b3e1f94150f17b0a27b3c13a722164e5118512691cf248d" +content-hash = "0d56bfdf88b0280475309ade51b6bb230ab96cc6111a7dbe8291c7aba12b5c20" diff --git a/pyproject.toml b/pyproject.toml index b6b53d6..116373b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ cssselect = "^1.1.0" curlify = "^2.2.1" [tool.poetry.dev-dependencies] -pytest = "^6.2.2" +pytest = "^8.3.2" black = "^22.10.0" flake8 = "^7.1.0" isort = "^5.8.0" From 9dd6121d4da71c6bbb2785e25118b5c635e07b5f Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 12:33:48 +0200 Subject: [PATCH 21/38] Update coinmarketcap error handling and tests. --- src/pricehist/sources/coinmarketcap.py | 26 +- tests/pricehist/sources/test_coinmarketcap.py | 285 ++++-------------- .../test_coinmarketcap/fiat-partial.json | 30 -- .../long-btc-aud-partial.json | 255 ---------------- .../test_coinmarketcap/recent-btc-aud.json | 136 --------- .../test_coinmarketcap/recent-btc-id2782.json | 136 --------- .../test_coinmarketcap/recent-id1-aud.json | 136 --------- .../test_coinmarketcap/recent-id1-id2782.json | 191 ++++++------ 8 files changed, 170 insertions(+), 1025 deletions(-) delete mode 100644 tests/pricehist/sources/test_coinmarketcap/fiat-partial.json delete mode 100644 tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json delete mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json delete mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json delete mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index 275df76..f7e0fb8 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -117,21 +117,6 @@ class CoinMarketCap(BaseSource): series.base, series.quote, self, "Bad quote ID." ) - elif code == 400 and "must be older than" in text: - if series.start <= series.end: - raise exceptions.BadResponse("The start date must be in the past.") - else: - raise exceptions.BadResponse( - "The start date must preceed or match the end date." - ) - - elif ( - code == 400 - and "must be a valid ISO 8601 timestamp or unix time" in text - and series.start < "2001-09-11" - ): - raise exceptions.BadResponse("The start date can't preceed 2001-09-11.") - try: response.raise_for_status() except Exception as e: @@ -142,6 +127,17 @@ class CoinMarketCap(BaseSource): except Exception as e: raise exceptions.ResponseParsingError(str(e)) from e + if ( + "status" in parsed + and "error_code" in parsed["status"] + and parsed["status"]["error_code"] == "500" + and "The system is busy" in parsed["status"]["error_message"] + ): + raise exceptions.BadResponse( + "The server indicated a general error. " + "There may be problem with your request." + ) + if type(parsed) is not dict or "data" not in parsed: raise exceptions.ResponseParsingError("Unexpected content.") diff --git a/tests/pricehist/sources/test_coinmarketcap.py b/tests/pricehist/sources/test_coinmarketcap.py index a7fec0c..b7bd721 100644 --- a/tests/pricehist/sources/test_coinmarketcap.py +++ b/tests/pricehist/sources/test_coinmarketcap.py @@ -36,9 +36,10 @@ def requests_mock(): yield mock -crypto_url = "https://web-api.coinmarketcap.com/v1/cryptocurrency/map?sort=cmc_rank" -fiat_url = "https://web-api.coinmarketcap.com/v1/fiat/map?include_metals=true" -fetch_url = "https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical" +crypto_url = ( + "https://api.coinmarketcap.com/data-api/v1/cryptocurrency/map?sort=cmc_rank" +) +fetch_url = "https://api.coinmarketcap.com/data-api/v3.1/cryptocurrency/historical" @pytest.fixture @@ -48,13 +49,6 @@ def crypto_ok(requests_mock): yield requests_mock -@pytest.fixture -def fiat_ok(requests_mock): - json = (Path(os.path.splitext(__file__)[0]) / "fiat-partial.json").read_text() - requests_mock.add(responses.GET, fiat_url, body=json, status=200) - yield requests_mock - - @pytest.fixture def recent_id_id_ok(requests_mock): json = (Path(os.path.splitext(__file__)[0]) / "recent-id1-id2782.json").read_text() @@ -62,36 +56,6 @@ def recent_id_id_ok(requests_mock): yield requests_mock -@pytest.fixture -def recent_id_sym_ok(requests_mock): - json = (Path(os.path.splitext(__file__)[0]) / "recent-id1-aud.json").read_text() - requests_mock.add(responses.GET, fetch_url, body=json, status=200) - yield requests_mock - - -@pytest.fixture -def recent_sym_id_ok(requests_mock): - json = (Path(os.path.splitext(__file__)[0]) / "recent-btc-id2782.json").read_text() - requests_mock.add(responses.GET, fetch_url, body=json, status=200) - yield requests_mock - - -@pytest.fixture -def recent_sym_sym_ok(requests_mock): - json = (Path(os.path.splitext(__file__)[0]) / "recent-btc-aud.json").read_text() - requests_mock.add(responses.GET, fetch_url, body=json, status=200) - yield requests_mock - - -@pytest.fixture -def long_sym_sym_ok(requests_mock): - json = ( - Path(os.path.splitext(__file__)[0]) / "long-btc-aud-partial.json" - ).read_text() - requests_mock.add(responses.GET, fetch_url, body=json, status=200) - yield requests_mock - - def test_normalizesymbol(src): assert src.normalizesymbol("btc") == "BTC" assert src.normalizesymbol("id=1") == "ID=1" @@ -120,63 +84,31 @@ def test_metadata(src): assert isinstance(src.notes(), str) -def test_symbols(src, crypto_ok, fiat_ok): +def test_symbols(src, crypto_ok): syms = src.symbols() assert ("id=1", "BTC Bitcoin") in syms assert ("id=2782", "AUD Australian Dollar") in syms assert len(syms) > 2 -def test_symbols_requests_logged(src, crypto_ok, fiat_ok, caplog): +def test_symbols_request_logged(src, crypto_ok, caplog): with caplog.at_level(logging.DEBUG): src.symbols() logged_requests = 0 for r in caplog.records: if r.levelname == "DEBUG" and "curl " in r.message: logged_requests += 1 - assert logged_requests == 2 + assert logged_requests == 1 -def test_symbols_fiat_not_found(src, requests_mock): - requests_mock.add(responses.GET, fiat_url, body="{}", status=200) - with pytest.raises(exceptions.ResponseParsingError) as e: - src.symbols() - assert "Unexpected content" in str(e.value) - - -def test_symbols_fiat_network_issue(src, requests_mock): - requests_mock.add( - responses.GET, - fiat_url, - body=requests.exceptions.ConnectionError("Network issue"), - ) - with pytest.raises(exceptions.RequestError) as e: - src.symbols() - assert "Network issue" in str(e.value) - - -def test_symbols_fiat_bad_status(src, requests_mock): - requests_mock.add(responses.GET, fiat_url, status=500) - with pytest.raises(exceptions.BadResponse) as e: - src.symbols() - assert "Server Error" in str(e.value) - - -def test_symbols_fiat_parsing_error(src, requests_mock): - requests_mock.add(responses.GET, fiat_url, body="NOT JSON") - with pytest.raises(exceptions.ResponseParsingError) as e: - src.symbols() - assert "while parsing data" in str(e.value) - - -def test_symbols_crypto_not_found(src, requests_mock, fiat_ok): +def test_symbols_crypto_not_found(src, requests_mock): requests_mock.add(responses.GET, crypto_url, body="{}", status=200) with pytest.raises(exceptions.ResponseParsingError) as e: src.symbols() assert "Unexpected content" in str(e.value) -def test_symbols_crypto_network_issue(src, requests_mock, fiat_ok): +def test_symbols_crypto_network_issue(src, requests_mock): requests_mock.add( responses.GET, crypto_url, @@ -187,14 +119,14 @@ def test_symbols_crypto_network_issue(src, requests_mock, fiat_ok): assert "Network issue" in str(e.value) -def test_symbols_crypto_bad_status(src, requests_mock, fiat_ok): +def test_symbols_crypto_bad_status(src, requests_mock): requests_mock.add(responses.GET, crypto_url, status=500) with pytest.raises(exceptions.BadResponse) as e: src.symbols() assert "Server Error" in str(e.value) -def test_symbols_crypto_parsing_error(src, requests_mock, fiat_ok): +def test_symbols_crypto_parsing_error(src, requests_mock): requests_mock.add(responses.GET, crypto_url, body="NOT JSON") with pytest.raises(exceptions.ResponseParsingError) as e: src.symbols() @@ -202,59 +134,59 @@ def test_symbols_crypto_parsing_error(src, requests_mock, fiat_ok): def test_symbols_no_data(src, type, requests_mock): - requests_mock.add(responses.GET, fiat_url, body='{"data": []}') + requests_mock.add(responses.GET, crypto_url, body='{"data": []}') with pytest.raises(exceptions.ResponseParsingError) as e: src.symbols() assert "Empty data section" in str(e.value) -def test_fetch_known_pair_id_id(src, type, recent_id_id_ok, crypto_ok, fiat_ok): +def test_fetch_known_pair_id_id(src, type, recent_id_id_ok, crypto_ok): series = src.fetch(Series("ID=1", "ID=2782", type, "2021-01-01", "2021-01-07")) req = recent_id_id_ok.calls[0].request assert req.params["id"] == "1" - assert req.params["convert_id"] == "2782" + assert req.params["convertId"] == "2782" assert (series.base, series.quote) == ("BTC", "AUD") assert len(series.prices) == 7 -def test_fetch_known_pair_id_sym(src, type, recent_id_sym_ok): +def test_fetch_known_pair_id_sym(src, type, recent_id_id_ok, crypto_ok): series = src.fetch(Series("ID=1", "AUD", type, "2021-01-01", "2021-01-07")) - req = recent_id_sym_ok.calls[0].request + req = recent_id_id_ok.calls[1].request assert req.params["id"] == "1" - assert req.params["convert"] == "AUD" + assert req.params["convertId"] == "2782" assert (series.base, series.quote) == ("BTC", "AUD") assert len(series.prices) == 7 -def test_fetch_known_pair_sym_id(src, type, recent_sym_id_ok, crypto_ok, fiat_ok): +def test_fetch_known_pair_sym_id(src, type, recent_id_id_ok, crypto_ok): series = src.fetch(Series("BTC", "ID=2782", type, "2021-01-01", "2021-01-07")) - req = recent_sym_id_ok.calls[0].request - assert req.params["symbol"] == "BTC" - assert req.params["convert_id"] == "2782" + req = recent_id_id_ok.calls[1].request + assert req.params["id"] == "1" + assert req.params["convertId"] == "2782" assert (series.base, series.quote) == ("BTC", "AUD") assert len(series.prices) == 7 -def test_fetch_known_pair_sym_sym(src, type, recent_sym_sym_ok): +def test_fetch_known_pair_sym_sym(src, type, recent_id_id_ok, crypto_ok): series = src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) - req = recent_sym_sym_ok.calls[0].request - assert req.params["symbol"] == "BTC" - assert req.params["convert"] == "AUD" + req = recent_id_id_ok.calls[1].request + assert req.params["id"] == "1" + assert req.params["convertId"] == "2782" assert len(series.prices) == 7 def test_fetch_requests_and_receives_correct_times( - src, type, recent_id_id_ok, crypto_ok, fiat_ok + src, type, recent_id_id_ok, crypto_ok ): series = src.fetch(Series("ID=1", "ID=2782", type, "2021-01-01", "2021-01-07")) req = recent_id_id_ok.calls[0].request - assert req.params["time_start"] == str(timestamp("2020-12-31")) # back one period - assert req.params["time_end"] == str(timestamp("2021-01-07")) - assert series.prices[0] == Price("2021-01-01", Decimal("37914.350602379853")) - assert series.prices[-1] == Price("2021-01-07", Decimal("49370.064689585612")) + assert req.params["timeStart"] == str(timestamp("2020-12-31")) # back one period + assert req.params["timeEnd"] == str(timestamp("2021-01-07")) + assert series.prices[0] == Price("2021-01-01", Decimal("37914.35060237985")) + assert series.prices[-1] == Price("2021-01-07", Decimal("49369.66288590665")) -def test_fetch_requests_logged(src, type, recent_sym_sym_ok, caplog): +def test_fetch_requests_logged(src, type, crypto_ok, recent_id_id_ok, caplog): with caplog.at_level(logging.DEBUG): src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) assert any( @@ -262,20 +194,20 @@ def test_fetch_requests_logged(src, type, recent_sym_sym_ok, caplog): ) -def test_fetch_types_all_available(src, recent_sym_sym_ok): +def test_fetch_types_all_available(src, crypto_ok, recent_id_id_ok): mid = src.fetch(Series("BTC", "AUD", "mid", "2021-01-01", "2021-01-07")) opn = src.fetch(Series("BTC", "AUD", "open", "2021-01-01", "2021-01-07")) hgh = src.fetch(Series("BTC", "AUD", "high", "2021-01-01", "2021-01-07")) low = src.fetch(Series("BTC", "AUD", "low", "2021-01-01", "2021-01-07")) cls = src.fetch(Series("BTC", "AUD", "close", "2021-01-01", "2021-01-07")) - assert mid.prices[0].amount == Decimal("37914.350602379853") - assert opn.prices[0].amount == Decimal("37658.83948707033") + assert mid.prices[0].amount == Decimal("37914.35060237985") + assert opn.prices[0].amount == Decimal("37658.1146368474") assert hgh.prices[0].amount == Decimal("38417.9137031205") - assert low.prices[0].amount == Decimal("37410.787501639206") - assert cls.prices[0].amount == Decimal("38181.99133300758") + assert low.prices[0].amount == Decimal("37410.7875016392") + assert cls.prices[0].amount == Decimal("38181.9913330076") -def test_fetch_type_mid_is_mean_of_low_and_high(src, recent_sym_sym_ok): +def test_fetch_type_mid_is_mean_of_low_and_high(src, crypto_ok, recent_id_id_ok): mid = src.fetch(Series("BTC", "AUD", "mid", "2021-01-01", "2021-01-07")).prices low = src.fetch(Series("BTC", "AUD", "low", "2021-01-01", "2021-01-07")).prices hgh = src.fetch(Series("BTC", "AUD", "high", "2021-01-01", "2021-01-07")).prices @@ -287,80 +219,24 @@ def test_fetch_type_mid_is_mean_of_low_and_high(src, recent_sym_sym_ok): ) -def test_fetch_long_hist_from_start(src, type, long_sym_sym_ok): - series = src.fetch(Series("BTC", "AUD", type, src.start(), "2021-01-07")) - assert series.prices[0] == Price("2013-04-28", Decimal("130.45956234123247")) - assert series.prices[-1] == Price("2021-01-07", Decimal("49370.064689585612")) - assert len(series.prices) > 13 - - -def test_fetch_from_before_start(src, type, requests_mock): - requests_mock.add( - responses.GET, - fetch_url, - status=400, - body="""{ "status": { "error_code": 400, "error_message": - "\\"time_start\\" must be a valid ISO 8601 timestamp or unix time value", - } }""", - ) - with pytest.raises(exceptions.BadResponse) as e: - src.fetch(Series("BTC", "AUD", type, "2001-09-10", "2001-10-01")) - assert "start date can't preceed" in str(e.value) - - -def test_fetch_to_future(src, type, recent_sym_sym_ok): - series = src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2100-01-01")) - assert len(series.prices) > 0 - - -def test_fetch_in_future(src, type, requests_mock): - requests_mock.add( - responses.GET, - fetch_url, - status=400, - body="""{ - "status": { - "error_code": 400, - "error_message": "\\"time_start\\" must be older than \\"time_end\\"." - } - }""", - ) - with pytest.raises(exceptions.BadResponse) as e: - src.fetch(Series("BTC", "AUD", type, "2030-01-01", "2030-01-07")) - assert "start date must be in the past" in str(e.value) - - -def test_fetch_reversed_dates(src, type, requests_mock): - requests_mock.add( - responses.GET, - fetch_url, - status=400, - body="""{ - "status": { - "error_code": 400, - "error_message": "\\"time_start\\" must be older than \\"time_end\\"." - } - }""", - ) - with pytest.raises(exceptions.BadResponse) as e: - src.fetch(Series("BTC", "AUD", type, "2021-01-07", "2021-01-01")) - assert "start date must preceed or match the end" in str(e.value) - - -def test_fetch_empty(src, type, requests_mock): +def test_fetch_empty(src, type, crypto_ok, requests_mock): requests_mock.add( responses.GET, fetch_url, body="""{ - "status": { - "error_code": 0, - "error_message": null - }, "data": { "id": 1, "name": "Bitcoin", "symbol": "BTC", + "timeEnd": "1228348799", "quotes": [] + }, + "status": { + "timestamp": "2024-08-03T09:31:52.719Z", + "error_code": "0", + "error_message": "SUCCESS", + "elapsed": "14", + "credit_count": 0 } }""", ) @@ -368,63 +244,36 @@ def test_fetch_empty(src, type, requests_mock): assert len(series.prices) == 0 -def test_fetch_bad_base_sym(src, type, requests_mock): - requests_mock.add(responses.GET, fetch_url, body='{"data":{}}') - with pytest.raises(exceptions.ResponseParsingError) as e: +def test_fetch_bad_base_sym(src, type, crypto_ok): + with pytest.raises(exceptions.InvalidPair) as e: src.fetch(Series("NOTABASE", "USD", type, "2021-01-01", "2021-01-07")) - assert "quote currency symbol can't be found" in str(e.value) - assert "other reasons" in str(e.value) + assert "Invalid symbol 'NOTABASE'" in str(e.value) -def test_fetch_bad_quote_sym(src, type, requests_mock): - requests_mock.add( - responses.GET, - fetch_url, - status=400, - body="""{ - "status": { - "error_code": 400, - "error_message": "Invalid value for \\"convert\\": \\"NOTAQUOTE\\"" - } - }""", - ) +def test_fetch_bad_quote_sym(src, type, crypto_ok): with pytest.raises(exceptions.InvalidPair) as e: src.fetch(Series("BTC", "NOTAQUOTE", type, "2021-01-01", "2021-01-07")) - assert "Bad quote symbol" in str(e.value) + assert "Invalid symbol 'NOTAQUOTE'" in str(e.value) -def test_fetch_bad_base_id(src, type, requests_mock): +def test_fetch_bad_response(src, type, crypto_ok, requests_mock): requests_mock.add( responses.GET, fetch_url, - status=400, + status=200, body="""{ - "status": { - "error_code": 400, - "error_message": "No items found." - } + "status": { + "timestamp": "2024-08-03T09:42:43.699Z", + "error_code": "500", + "error_message": "The system is busy, please try again later!", + "elapsed": "0", + "credit_count": 0 + } }""", ) - with pytest.raises(exceptions.InvalidPair) as e: - src.fetch(Series("ID=20000", "USD", type, "2021-01-01", "2021-01-07")) - assert "Bad base ID" in str(e.value) - - -def test_fetch_bad_quote_id(src, type, requests_mock): - requests_mock.add( - responses.GET, - fetch_url, - status=400, - body="""{ - "status": { - "error_code": 400, - "error_message": "Invalid value for \\"convert_id\\": \\"20000\\"" - } - }""", - ) - with pytest.raises(exceptions.InvalidPair) as e: - src.fetch(Series("BTC", "ID=20000", type, "2021-01-01", "2021-01-07")) - assert "Bad quote ID" in str(e.value) + with pytest.raises(exceptions.BadResponse) as e: + src.fetch(Series("ID=987654321", "USD", type, "2021-01-01", "2021-01-07")) + assert "general error" in str(e.value) def test_fetch_no_quote(src, type): @@ -432,7 +281,7 @@ def test_fetch_no_quote(src, type): src.fetch(Series("BTC", "", type, "2021-01-01", "2021-01-07")) -def test_fetch_network_issue(src, type, requests_mock): +def test_fetch_network_issue(src, type, crypto_ok, requests_mock): body = requests.exceptions.ConnectionError("Network issue") requests_mock.add(responses.GET, fetch_url, body=body) with pytest.raises(exceptions.RequestError) as e: @@ -440,21 +289,21 @@ def test_fetch_network_issue(src, type, requests_mock): assert "Network issue" in str(e.value) -def test_fetch_bad_status(src, type, requests_mock): +def test_fetch_bad_status(src, type, crypto_ok, requests_mock): requests_mock.add(responses.GET, fetch_url, status=500, body="Some other reason") with pytest.raises(exceptions.BadResponse) as e: src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) assert "Internal Server Error" in str(e.value) -def test_fetch_parsing_error(src, type, requests_mock): +def test_fetch_parsing_error(src, type, crypto_ok, requests_mock): requests_mock.add(responses.GET, fetch_url, body="NOT JSON") with pytest.raises(exceptions.ResponseParsingError) as e: src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) assert "while parsing data" in str(e.value) -def test_fetch_unexpected_json(src, type, requests_mock): +def test_fetch_unexpected_json(src, type, crypto_ok, requests_mock): requests_mock.add(responses.GET, fetch_url, body='{"notdata": []}') with pytest.raises(exceptions.ResponseParsingError) as e: src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) diff --git a/tests/pricehist/sources/test_coinmarketcap/fiat-partial.json b/tests/pricehist/sources/test_coinmarketcap/fiat-partial.json deleted file mode 100644 index 781824b..0000000 --- a/tests/pricehist/sources/test_coinmarketcap/fiat-partial.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "status": { - "timestamp": "2021-07-16T10:08:13.272Z", - "error_code": 0, - "error_message": null, - "elapsed": 1, - "credit_count": 0, - "notice": null - }, - "data": [ - { - "id": 2781, - "name": "United States Dollar", - "sign": "$", - "symbol": "USD" - }, - { - "id": 2782, - "name": "Australian Dollar", - "sign": "$", - "symbol": "AUD" - }, - { - "id": 3575, - "name": "Gold Troy Ounce", - "symbol": "", - "code": "XAU" - } - ] -} diff --git a/tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json b/tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json deleted file mode 100644 index 0b11696..0000000 --- a/tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json +++ /dev/null @@ -1,255 +0,0 @@ -{ - "status": { - "timestamp": "2021-07-17T16:16:11.926Z", - "error_code": 0, - "error_message": null, - "elapsed": 2262, - "credit_count": 0, - "notice": null - }, - "data": { - "id": 1, - "name": "Bitcoin", - "symbol": "BTC", - "quotes": [ - { - "time_open": "2013-04-28T00:00:00.000Z", - "time_close": "2013-04-28T23:59:59.999Z", - "time_high": "2013-04-28T18:50:02.000Z", - "time_low": "2013-04-28T20:15:02.000Z", - "quote": { - "AUD": { - "open": null, - "high": 132.39216797540558, - "low": 128.52695670705936, - "close": 130.52908647526473, - "volume": 0, - "market_cap": 1447740447.626921, - "timestamp": "2013-04-28T23:59:00.000Z" - } - } - }, - { - "time_open": "2013-04-29T00:00:00.000Z", - "time_close": "2013-04-29T23:59:59.999Z", - "time_high": "2013-04-29T13:15:01.000Z", - "time_low": "2013-04-29T05:20:01.000Z", - "quote": { - "AUD": { - "open": 130.75666236543535, - "high": 142.67970067891736, - "low": 129.9456943366951, - "close": 139.77370978254794, - "volume": 0, - "market_cap": 1550883729.329852, - "timestamp": "2013-04-29T23:59:00.000Z" - } - } - }, - { - "time_open": "2013-04-30T00:00:00.000Z", - "time_close": "2013-04-30T23:59:59.999Z", - "time_high": "2013-04-30T08:25:02.000Z", - "time_low": "2013-04-30T18:55:01.000Z", - "quote": { - "AUD": { - "open": 139.2515230635335, - "high": 141.93391873626476, - "low": 129.37940647790543, - "close": 134.06635802469137, - "volume": 0, - "market_cap": 1488052782.6003087, - "timestamp": "2013-04-30T23:59:00.000Z" - } - } - }, - { - "time_open": "2013-05-01T00:00:00.000Z", - "time_close": "2013-05-01T23:59:59.999Z", - "time_high": "2013-05-01T00:15:01.000Z", - "time_low": "2013-05-01T19:55:01.000Z", - "quote": { - "AUD": { - "open": 134.06635802469137, - "high": 134.88573849160971, - "low": 104.93911468163968, - "close": 113.79243056489595, - "volume": 0, - "market_cap": 1263451603.6864119, - "timestamp": "2013-05-01T23:59:00.000Z" - } - } - }, - { - "time_open": "2013-05-02T00:00:00.000Z", - "time_close": "2013-05-02T23:59:59.999Z", - "time_high": "2013-05-02T14:25:01.000Z", - "time_low": "2013-05-02T14:30:02.000Z", - "quote": { - "AUD": { - "open": 113.19910247390133, - "high": 122.60835462135991, - "low": 90.08385249759387, - "close": 102.63388848353591, - "volume": 0, - "market_cap": 1139905858.2089553, - "timestamp": "2013-05-02T23:59:00.000Z" - } - } - }, - { - "time_open": "2013-05-03T00:00:00.000Z", - "time_close": "2013-05-03T23:59:59.999Z", - "time_high": "2013-05-03T05:30:02.000Z", - "time_low": "2013-05-03T03:05:01.000Z", - "quote": { - "AUD": { - "open": 103.64842454394694, - "high": 105.43929629649027, - "low": 77.03544845551335, - "close": 94.77409346519293, - "volume": 0, - "market_cap": 1052933070.3412836, - "timestamp": "2013-05-03T23:59:00.000Z" - } - } - }, - { - "time_open": "2013-05-04T00:00:00.000Z", - "time_close": "2013-05-04T23:59:59.999Z", - "time_high": "2013-05-04T07:15:01.000Z", - "time_low": "2013-05-04T06:50:01.000Z", - "quote": { - "AUD": { - "open": 95.11343656595025, - "high": 111.49893348846227, - "low": 89.68392476245879, - "close": 109.07504363001745, - "volume": 0, - "market_cap": 1212251854.2757416, - "timestamp": "2013-05-04T23:59:00.000Z" - } - } - }, - { - "time_open": "2021-01-01T00:00:00.000Z", - "time_close": "2021-01-01T23:59:59.999Z", - "time_high": "2021-01-01T12:38:43.000Z", - "time_low": "2021-01-01T00:16:43.000Z", - "quote": { - "AUD": { - "open": 37658.83948707033, - "high": 38417.9137031205, - "low": 37410.787501639206, - "close": 38181.99133300758, - "volume": 52943282221.028366, - "market_cap": 709720173049.5383, - "timestamp": "2021-01-01T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-02T00:00:00.000Z", - "time_close": "2021-01-02T23:59:59.999Z", - "time_high": "2021-01-02T19:49:42.000Z", - "time_low": "2021-01-02T00:31:44.000Z", - "quote": { - "AUD": { - "open": 38184.98611600682, - "high": 43096.681197423015, - "low": 37814.17187096531, - "close": 41760.62923079505, - "volume": 88214867181.97835, - "market_cap": 776278147177.8037, - "timestamp": "2021-01-02T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-03T00:00:00.000Z", - "time_close": "2021-01-03T23:59:59.999Z", - "time_high": "2021-01-03T07:47:38.000Z", - "time_low": "2021-01-03T00:20:45.000Z", - "quote": { - "AUD": { - "open": 41763.41015117659, - "high": 44985.93247585023, - "low": 41663.204350601605, - "close": 42511.10646879765, - "volume": 102011582370.28117, - "market_cap": 790270288834.0249, - "timestamp": "2021-01-03T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-04T00:00:00.000Z", - "time_close": "2021-01-04T23:59:59.999Z", - "time_high": "2021-01-04T04:07:42.000Z", - "time_low": "2021-01-04T10:19:42.000Z", - "quote": { - "AUD": { - "open": 42548.61349648768, - "high": 43360.96165147421, - "low": 37133.98436952697, - "close": 41686.38761359174, - "volume": 105824510346.65779, - "market_cap": 774984045201.7122, - "timestamp": "2021-01-04T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-05T00:00:00.000Z", - "time_close": "2021-01-05T23:59:59.999Z", - "time_high": "2021-01-05T22:44:35.000Z", - "time_low": "2021-01-05T06:16:41.000Z", - "quote": { - "AUD": { - "open": 41693.07321807638, - "high": 44403.79487147647, - "low": 39221.81167941294, - "close": 43790.067253370056, - "volume": 87016490203.50436, - "market_cap": 814135603090.2502, - "timestamp": "2021-01-05T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-06T00:00:00.000Z", - "time_close": "2021-01-06T23:59:59.999Z", - "time_high": "2021-01-06T23:57:36.000Z", - "time_low": "2021-01-06T00:25:38.000Z", - "quote": { - "AUD": { - "open": 43817.35864984641, - "high": 47186.65232598287, - "low": 43152.60281764236, - "close": 47115.85365360005, - "volume": 96330948324.8061, - "market_cap": 876019742889.9551, - "timestamp": "2021-01-06T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-07T00:00:00.000Z", - "time_close": "2021-01-07T23:59:59.999Z", - "time_high": "2021-01-07T18:17:42.000Z", - "time_low": "2021-01-07T08:25:51.000Z", - "quote": { - "AUD": { - "open": 47128.02139328098, - "high": 51833.478207775144, - "low": 46906.65117139608, - "close": 50686.90986207153, - "volume": 109124136558.20264, - "market_cap": 942469208700.134, - "timestamp": "2021-01-07T23:59:06.000Z" - } - } - } - ] - } -} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json b/tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json deleted file mode 100644 index 342b824..0000000 --- a/tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "status": { - "timestamp": "2021-07-16T10:42:32.013Z", - "error_code": 0, - "error_message": null, - "elapsed": 20, - "credit_count": 0, - "notice": null - }, - "data": { - "id": 1, - "name": "Bitcoin", - "symbol": "BTC", - "quotes": [ - { - "time_open": "2021-01-01T00:00:00.000Z", - "time_close": "2021-01-01T23:59:59.999Z", - "time_high": "2021-01-01T12:38:43.000Z", - "time_low": "2021-01-01T00:16:43.000Z", - "quote": { - "AUD": { - "open": 37658.83948707033, - "high": 38417.9137031205, - "low": 37410.787501639206, - "close": 38181.99133300758, - "volume": 52943282221.028366, - "market_cap": 709720173049.5383, - "timestamp": "2021-01-01T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-02T00:00:00.000Z", - "time_close": "2021-01-02T23:59:59.999Z", - "time_high": "2021-01-02T19:49:42.000Z", - "time_low": "2021-01-02T00:31:44.000Z", - "quote": { - "AUD": { - "open": 38184.98611600682, - "high": 43096.681197423015, - "low": 37814.17187096531, - "close": 41760.62923079505, - "volume": 88214867181.97835, - "market_cap": 776278147177.8037, - "timestamp": "2021-01-02T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-03T00:00:00.000Z", - "time_close": "2021-01-03T23:59:59.999Z", - "time_high": "2021-01-03T07:47:38.000Z", - "time_low": "2021-01-03T00:20:45.000Z", - "quote": { - "AUD": { - "open": 41763.41015117659, - "high": 44985.93247585023, - "low": 41663.204350601605, - "close": 42511.10646879765, - "volume": 102011582370.28117, - "market_cap": 790270288834.0249, - "timestamp": "2021-01-03T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-04T00:00:00.000Z", - "time_close": "2021-01-04T23:59:59.999Z", - "time_high": "2021-01-04T04:07:42.000Z", - "time_low": "2021-01-04T10:19:42.000Z", - "quote": { - "AUD": { - "open": 42548.61349648768, - "high": 43360.96165147421, - "low": 37133.98436952697, - "close": 41686.38761359174, - "volume": 105824510346.65779, - "market_cap": 774984045201.7122, - "timestamp": "2021-01-04T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-05T00:00:00.000Z", - "time_close": "2021-01-05T23:59:59.999Z", - "time_high": "2021-01-05T22:44:35.000Z", - "time_low": "2021-01-05T06:16:41.000Z", - "quote": { - "AUD": { - "open": 41693.07321807638, - "high": 44403.79487147647, - "low": 39221.81167941294, - "close": 43790.067253370056, - "volume": 87016490203.50436, - "market_cap": 814135603090.2502, - "timestamp": "2021-01-05T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-06T00:00:00.000Z", - "time_close": "2021-01-06T23:59:59.999Z", - "time_high": "2021-01-06T23:57:36.000Z", - "time_low": "2021-01-06T00:25:38.000Z", - "quote": { - "AUD": { - "open": 43817.35864984641, - "high": 47186.65232598287, - "low": 43152.60281764236, - "close": 47115.85365360005, - "volume": 96330948324.8061, - "market_cap": 876019742889.9551, - "timestamp": "2021-01-06T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-07T00:00:00.000Z", - "time_close": "2021-01-07T23:59:59.999Z", - "time_high": "2021-01-07T18:17:42.000Z", - "time_low": "2021-01-07T08:25:51.000Z", - "quote": { - "AUD": { - "open": 47128.02139328098, - "high": 51833.478207775144, - "low": 46906.65117139608, - "close": 50686.90986207153, - "volume": 109124136558.20264, - "market_cap": 942469208700.134, - "timestamp": "2021-01-07T23:59:06.000Z" - } - } - } - ] - } -} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json b/tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json deleted file mode 100644 index 8614b07..0000000 --- a/tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "status": { - "timestamp": "2021-07-16T10:42:27.169Z", - "error_code": 0, - "error_message": null, - "elapsed": 19, - "credit_count": 0, - "notice": null - }, - "data": { - "id": 1, - "name": "Bitcoin", - "symbol": "BTC", - "quotes": [ - { - "time_open": "2021-01-01T00:00:00.000Z", - "time_close": "2021-01-01T23:59:59.999Z", - "time_high": "2021-01-01T12:38:43.000Z", - "time_low": "2021-01-01T00:16:43.000Z", - "quote": { - "2782": { - "open": 37658.83948707033, - "high": 38417.9137031205, - "low": 37410.787501639206, - "close": 38181.99133300758, - "volume": 52943282221.028366, - "market_cap": 709720173049.5383, - "timestamp": "2021-01-01T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-02T00:00:00.000Z", - "time_close": "2021-01-02T23:59:59.999Z", - "time_high": "2021-01-02T19:49:42.000Z", - "time_low": "2021-01-02T00:31:44.000Z", - "quote": { - "2782": { - "open": 38184.98611600682, - "high": 43096.681197423015, - "low": 37814.17187096531, - "close": 41760.62923079505, - "volume": 88214867181.97835, - "market_cap": 776278147177.8037, - "timestamp": "2021-01-02T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-03T00:00:00.000Z", - "time_close": "2021-01-03T23:59:59.999Z", - "time_high": "2021-01-03T07:47:38.000Z", - "time_low": "2021-01-03T00:20:45.000Z", - "quote": { - "2782": { - "open": 41763.41015117659, - "high": 44985.93247585023, - "low": 41663.204350601605, - "close": 42511.10646879765, - "volume": 102011582370.28117, - "market_cap": 790270288834.0249, - "timestamp": "2021-01-03T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-04T00:00:00.000Z", - "time_close": "2021-01-04T23:59:59.999Z", - "time_high": "2021-01-04T04:07:42.000Z", - "time_low": "2021-01-04T10:19:42.000Z", - "quote": { - "2782": { - "open": 42548.61349648768, - "high": 43360.96165147421, - "low": 37133.98436952697, - "close": 41686.38761359174, - "volume": 105824510346.65779, - "market_cap": 774984045201.7122, - "timestamp": "2021-01-04T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-05T00:00:00.000Z", - "time_close": "2021-01-05T23:59:59.999Z", - "time_high": "2021-01-05T22:44:35.000Z", - "time_low": "2021-01-05T06:16:41.000Z", - "quote": { - "2782": { - "open": 41693.07321807638, - "high": 44403.79487147647, - "low": 39221.81167941294, - "close": 43790.067253370056, - "volume": 87016490203.50436, - "market_cap": 814135603090.2502, - "timestamp": "2021-01-05T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-06T00:00:00.000Z", - "time_close": "2021-01-06T23:59:59.999Z", - "time_high": "2021-01-06T23:57:36.000Z", - "time_low": "2021-01-06T00:25:38.000Z", - "quote": { - "2782": { - "open": 43817.35864984641, - "high": 47186.65232598287, - "low": 43152.60281764236, - "close": 47115.85365360005, - "volume": 96330948324.8061, - "market_cap": 876019742889.9551, - "timestamp": "2021-01-06T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-07T00:00:00.000Z", - "time_close": "2021-01-07T23:59:59.999Z", - "time_high": "2021-01-07T18:17:42.000Z", - "time_low": "2021-01-07T08:25:51.000Z", - "quote": { - "2782": { - "open": 47128.02139328098, - "high": 51833.478207775144, - "low": 46906.65117139608, - "close": 50686.90986207153, - "volume": 109124136558.20264, - "market_cap": 942469208700.134, - "timestamp": "2021-01-07T23:59:06.000Z" - } - } - } - ] - } -} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json b/tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json deleted file mode 100644 index 8b70b6a..0000000 --- a/tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "status": { - "timestamp": "2021-07-16T10:42:24.612Z", - "error_code": 0, - "error_message": null, - "elapsed": 57, - "credit_count": 0, - "notice": null - }, - "data": { - "id": 1, - "name": "Bitcoin", - "symbol": "BTC", - "quotes": [ - { - "time_open": "2021-01-01T00:00:00.000Z", - "time_close": "2021-01-01T23:59:59.999Z", - "time_high": "2021-01-01T12:38:43.000Z", - "time_low": "2021-01-01T00:16:43.000Z", - "quote": { - "AUD": { - "open": 37658.83948707033, - "high": 38417.9137031205, - "low": 37410.787501639206, - "close": 38181.99133300758, - "volume": 52943282221.028366, - "market_cap": 709720173049.5383, - "timestamp": "2021-01-01T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-02T00:00:00.000Z", - "time_close": "2021-01-02T23:59:59.999Z", - "time_high": "2021-01-02T19:49:42.000Z", - "time_low": "2021-01-02T00:31:44.000Z", - "quote": { - "AUD": { - "open": 38184.98611600682, - "high": 43096.681197423015, - "low": 37814.17187096531, - "close": 41760.62923079505, - "volume": 88214867181.97835, - "market_cap": 776278147177.8037, - "timestamp": "2021-01-02T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-03T00:00:00.000Z", - "time_close": "2021-01-03T23:59:59.999Z", - "time_high": "2021-01-03T07:47:38.000Z", - "time_low": "2021-01-03T00:20:45.000Z", - "quote": { - "AUD": { - "open": 41763.41015117659, - "high": 44985.93247585023, - "low": 41663.204350601605, - "close": 42511.10646879765, - "volume": 102011582370.28117, - "market_cap": 790270288834.0249, - "timestamp": "2021-01-03T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-04T00:00:00.000Z", - "time_close": "2021-01-04T23:59:59.999Z", - "time_high": "2021-01-04T04:07:42.000Z", - "time_low": "2021-01-04T10:19:42.000Z", - "quote": { - "AUD": { - "open": 42548.61349648768, - "high": 43360.96165147421, - "low": 37133.98436952697, - "close": 41686.38761359174, - "volume": 105824510346.65779, - "market_cap": 774984045201.7122, - "timestamp": "2021-01-04T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-05T00:00:00.000Z", - "time_close": "2021-01-05T23:59:59.999Z", - "time_high": "2021-01-05T22:44:35.000Z", - "time_low": "2021-01-05T06:16:41.000Z", - "quote": { - "AUD": { - "open": 41693.07321807638, - "high": 44403.79487147647, - "low": 39221.81167941294, - "close": 43790.067253370056, - "volume": 87016490203.50436, - "market_cap": 814135603090.2502, - "timestamp": "2021-01-05T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-06T00:00:00.000Z", - "time_close": "2021-01-06T23:59:59.999Z", - "time_high": "2021-01-06T23:57:36.000Z", - "time_low": "2021-01-06T00:25:38.000Z", - "quote": { - "AUD": { - "open": 43817.35864984641, - "high": 47186.65232598287, - "low": 43152.60281764236, - "close": 47115.85365360005, - "volume": 96330948324.8061, - "market_cap": 876019742889.9551, - "timestamp": "2021-01-06T23:59:06.000Z" - } - } - }, - { - "time_open": "2021-01-07T00:00:00.000Z", - "time_close": "2021-01-07T23:59:59.999Z", - "time_high": "2021-01-07T18:17:42.000Z", - "time_low": "2021-01-07T08:25:51.000Z", - "quote": { - "AUD": { - "open": 47128.02139328098, - "high": 51833.478207775144, - "low": 46906.65117139608, - "close": 50686.90986207153, - "volume": 109124136558.20264, - "market_cap": 942469208700.134, - "timestamp": "2021-01-07T23:59:06.000Z" - } - } - } - ] - } -} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json b/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json index d172453..e05e7bf 100644 --- a/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json +++ b/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json @@ -1,136 +1,129 @@ { - "status": { - "timestamp": "2021-07-16T10:42:21.065Z", - "error_code": 0, - "error_message": null, - "elapsed": 17, - "credit_count": 0, - "notice": null - }, "data": { "id": 1, "name": "Bitcoin", "symbol": "BTC", + "timeEnd": "1575503999", "quotes": [ { - "time_open": "2021-01-01T00:00:00.000Z", - "time_close": "2021-01-01T23:59:59.999Z", - "time_high": "2021-01-01T12:38:43.000Z", - "time_low": "2021-01-01T00:16:43.000Z", + "timeOpen": "2021-01-01T00:00:00.000Z", + "timeClose": "2021-01-01T23:59:59.999Z", + "timeHigh": "2021-01-01T12:38:43.000Z", + "timeLow": "2021-01-01T00:16:43.000Z", "quote": { - "2782": { - "open": 37658.83948707033, - "high": 38417.9137031205, - "low": 37410.787501639206, - "close": 38181.99133300758, - "volume": 52943282221.028366, - "market_cap": 709720173049.5383, - "timestamp": "2021-01-01T23:59:06.000Z" - } + "name": "2782", + "open": 37658.1146368474, + "high": 38417.9137031205, + "low": 37410.7875016392, + "close": 38181.9913330076, + "volume": 52901492931.8344367080, + "marketCap": 709159975413.2388897949, + "timestamp": "2021-01-01T23:59:59.999Z" } }, { - "time_open": "2021-01-02T00:00:00.000Z", - "time_close": "2021-01-02T23:59:59.999Z", - "time_high": "2021-01-02T19:49:42.000Z", - "time_low": "2021-01-02T00:31:44.000Z", + "timeOpen": "2021-01-02T00:00:00.000Z", + "timeClose": "2021-01-02T23:59:59.999Z", + "timeHigh": "2021-01-02T19:49:42.000Z", + "timeLow": "2021-01-02T00:31:44.000Z", "quote": { - "2782": { - "open": 38184.98611600682, - "high": 43096.681197423015, - "low": 37814.17187096531, - "close": 41760.62923079505, - "volume": 88214867181.97835, - "market_cap": 776278147177.8037, - "timestamp": "2021-01-02T23:59:06.000Z" - } + "name": "2782", + "open": 38184.9861160068, + "high": 43096.6811974230, + "low": 37814.1718709653, + "close": 41760.6292307951, + "volume": 88214867181.9830439141, + "marketCap": 776278147177.8037261338, + "timestamp": "2021-01-02T23:59:59.999Z" } }, { - "time_open": "2021-01-03T00:00:00.000Z", - "time_close": "2021-01-03T23:59:59.999Z", - "time_high": "2021-01-03T07:47:38.000Z", - "time_low": "2021-01-03T00:20:45.000Z", + "timeOpen": "2021-01-03T00:00:00.000Z", + "timeClose": "2021-01-03T23:59:59.999Z", + "timeHigh": "2021-01-03T07:47:38.000Z", + "timeLow": "2021-01-03T00:20:45.000Z", "quote": { - "2782": { - "open": 41763.41015117659, - "high": 44985.93247585023, - "low": 41663.204350601605, - "close": 42511.10646879765, - "volume": 102011582370.28117, - "market_cap": 790270288834.0249, - "timestamp": "2021-01-03T23:59:06.000Z" - } + "name": "2782", + "open": 41763.4101511766, + "high": 44985.9324758502, + "low": 41663.2043506016, + "close": 42534.0538859236, + "volume": 102253005977.1115650988, + "marketCap": 792140565709.1701340036, + "timestamp": "2021-01-03T23:59:59.999Z" } }, { - "time_open": "2021-01-04T00:00:00.000Z", - "time_close": "2021-01-04T23:59:59.999Z", - "time_high": "2021-01-04T04:07:42.000Z", - "time_low": "2021-01-04T10:19:42.000Z", + "timeOpen": "2021-01-04T00:00:00.000Z", + "timeClose": "2021-01-04T23:59:59.999Z", + "timeHigh": "2021-01-04T04:07:42.000Z", + "timeLow": "2021-01-04T10:19:42.000Z", "quote": { - "2782": { - "open": 42548.61349648768, - "high": 43360.96165147421, - "low": 37133.98436952697, - "close": 41686.38761359174, - "volume": 105824510346.65779, - "market_cap": 774984045201.7122, - "timestamp": "2021-01-04T23:59:06.000Z" - } + "name": "2782", + "open": 42548.6134964877, + "high": 43347.7527651400, + "low": 37111.8678479690, + "close": 41707.4890765162, + "volume": 105251252720.3013091567, + "marketCap": 770785910830.3801120744, + "timestamp": "2021-01-04T23:59:59.999Z" } }, { - "time_open": "2021-01-05T00:00:00.000Z", - "time_close": "2021-01-05T23:59:59.999Z", - "time_high": "2021-01-05T22:44:35.000Z", - "time_low": "2021-01-05T06:16:41.000Z", + "timeOpen": "2021-01-05T00:00:00.000Z", + "timeClose": "2021-01-05T23:59:59.999Z", + "timeHigh": "2021-01-05T22:44:35.000Z", + "timeLow": "2021-01-05T06:16:41.000Z", "quote": { - "2782": { - "open": 41693.07321807638, - "high": 44403.79487147647, - "low": 39221.81167941294, - "close": 43790.067253370056, - "volume": 87016490203.50436, - "market_cap": 814135603090.2502, - "timestamp": "2021-01-05T23:59:06.000Z" - } + "name": "2782", + "open": 41693.0732180764, + "high": 44406.6531914952, + "low": 39220.9654861842, + "close": 43777.4560620835, + "volume": 88071174132.6445648582, + "marketCap": 824003338903.4613958343, + "timestamp": "2021-01-05T23:59:59.999Z" } }, { - "time_open": "2021-01-06T00:00:00.000Z", - "time_close": "2021-01-06T23:59:59.999Z", - "time_high": "2021-01-06T23:57:36.000Z", - "time_low": "2021-01-06T00:25:38.000Z", + "timeOpen": "2021-01-06T00:00:00.000Z", + "timeClose": "2021-01-06T23:59:59.999Z", + "timeHigh": "2021-01-06T23:57:36.000Z", + "timeLow": "2021-01-06T00:25:38.000Z", "quote": { - "2782": { - "open": 43817.35864984641, - "high": 47186.65232598287, - "low": 43152.60281764236, - "close": 47115.85365360005, - "volume": 96330948324.8061, - "market_cap": 876019742889.9551, - "timestamp": "2021-01-06T23:59:06.000Z" - } + "name": "2782", + "open": 43798.3790529373, + "high": 47185.7303335186, + "low": 43152.6028176424, + "close": 47114.9330444897, + "volume": 96948095813.7503737302, + "marketCap": 881631993096.0701475336, + "timestamp": "2021-01-06T23:59:59.999Z" } }, { - "time_open": "2021-01-07T00:00:00.000Z", - "time_close": "2021-01-07T23:59:59.999Z", - "time_high": "2021-01-07T18:17:42.000Z", - "time_low": "2021-01-07T08:25:51.000Z", + "timeOpen": "2021-01-07T00:00:00.000Z", + "timeClose": "2021-01-07T23:59:59.999Z", + "timeHigh": "2021-01-07T18:17:42.000Z", + "timeLow": "2021-01-07T08:25:51.000Z", "quote": { - "2782": { - "open": 47128.02139328098, - "high": 51833.478207775144, - "low": 46906.65117139608, - "close": 50686.90986207153, - "volume": 109124136558.20264, - "market_cap": 942469208700.134, - "timestamp": "2021-01-07T23:59:06.000Z" - } + "name": "2782", + "open": 47128.0213932810, + "high": 51832.6746004172, + "low": 46906.6511713961, + "close": 50660.9643451606, + "volume": 108451040396.2660095877, + "marketCap": 936655898949.2177196744, + "timestamp": "2021-01-07T23:59:59.999Z" } } ] + }, + "status": { + "timestamp": "2024-08-02T18:23:21.586Z", + "error_code": "0", + "error_message": "SUCCESS", + "elapsed": "212", + "credit_count": 0 } } From 9eb6de4c440e7989ea86db9c8f2ce812abf54f5f Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 12:40:28 +0200 Subject: [PATCH 22/38] live tests: reactivate coinmarketcap, update alphavantage physical for new data. --- tests/live.sh | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/live.sh b/tests/live.sh index a25b93d..dd5f626 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -72,14 +72,13 @@ END run_test "$name" "$cmd" "$expected" name="Alpha Vantage physical currency" -cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-10 -e 2021-01-14" +cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-11 -e 2021-01-14" read -r -d '' expected < Date: Sat, 3 Aug 2024 12:54:11 +0200 Subject: [PATCH 23/38] Update coinmarketcap source notes. --- src/pricehist/sources/coinmarketcap.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index f7e0fb8..58eeaaf 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -33,13 +33,16 @@ class CoinMarketCap(BaseSource): def notes(self): return ( - "This source makes unoffical use of endpoints that power CoinMarketCap's " - "public web interface. The price data comes from a public equivalent of " - "the OHLCV Historical endpoint found in CoinMarketCap's official API.\n" - "CoinMarketCap currency symbols are not necessarily unique, so it " - "is recommended that you use IDs, which can be listed via the " - "--symbols option. For example, 'ETH/BTC' is 'id=1027/id=1'. The " - "corresponding symbols will be used in output." + "This source makes unoffical use of endpoints that power " + "CoinMarketCap's public web interface.\n" + "CoinMarketCap currency symbols are not necessarily unique. " + "Each symbol you give will be coverted an ID by checking fiat and " + "metals first, then crypto by CoinMarketCap rank. " + "The symbol data is hard-coded for fiat and metals, but fetched " + "live for crypto.\n" + "You can directly use IDs, which can be listed via the --symbols " + "option. For example, 'ETH/BTC' is 'id=1027/id=1'. " + "The corresponding symbols will be used in output, when available." ) def symbols(self): From 1e1003994ce84206082caac3a204a5e0bbb618f7 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 13:04:25 +0200 Subject: [PATCH 24/38] Version 1.4.8. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 116373b..9cf089a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.7" +version = "1.4.8" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index ac329c9..4963389 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.7" +__version__ = "1.4.8" From e8dec0bf64310ebf9e33af19c455ee3c72631c55 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 17:15:17 +0200 Subject: [PATCH 25/38] Update Alpha Vantage rate limit handling. --- src/pricehist/sources/alphavantage.py | 4 ++-- tests/pricehist/sources/test_alphavantage.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index e60f0f6..c42d490 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -337,8 +337,8 @@ class AlphaVantage(BaseSource): def _raise_for_generic_errors(self, data): if type(data) is dict: - if "Note" in data and "call frequency" in data["Note"]: - raise exceptions.RateLimit(data["Note"]) + if "Information" in data and "daily rate limits" in data["Information"]: + raise exceptions.RateLimit(data["Information"]) if ( "Information" in data and "unlock" in data["Information"] diff --git a/tests/pricehist/sources/test_alphavantage.py b/tests/pricehist/sources/test_alphavantage.py index 8ff70cc..40febb0 100644 --- a/tests/pricehist/sources/test_alphavantage.py +++ b/tests/pricehist/sources/test_alphavantage.py @@ -59,11 +59,11 @@ digital_url = re.compile( ) rate_limit_json = ( - '{ "Note": "' - "Thank you for using Alpha Vantage! Our standard API call frequency is 5 " - "calls per minute and 500 calls per day. Please visit " - "https://www.alphavantage.co/premium/ if you would like to target a higher " - "API call frequency." + '{ "Information": "' + "Thank you for using Alpha Vantage! Our standard API rate limit is 25 " + "requests per day. Please subscribe to any of the premium plans at " + "https://www.alphavantage.co/premium/ to instantly remove all daily rate " + "limits." '" }' ) From b7d0d739ab9841ef05e0793cac9041b0843640ff Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 17:19:36 +0200 Subject: [PATCH 26/38] Version 1.4.9. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9cf089a..d1a7ce1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.8" +version = "1.4.9" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index 4963389..1ad354e 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.8" +__version__ = "1.4.9" From 51e297b75252b44e4c8ba3f2bffbeb734c820b8f Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 3 Aug 2024 17:23:53 +0200 Subject: [PATCH 27/38] Update alphavantage source notes regarding API rate limit. --- src/pricehist/sources/alphavantage.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pricehist/sources/alphavantage.py b/src/pricehist/sources/alphavantage.py index c42d490..40d5b98 100644 --- a/src/pricehist/sources/alphavantage.py +++ b/src/pricehist/sources/alphavantage.py @@ -56,10 +56,8 @@ class AlphaVantage(BaseSource): "Beware that digital currencies quoted in non-USD currencies may " "be converted from USD data at one recent exchange rate rather " "than using historical rates.\n" - "Alpha Vantage's standard API call frequency limits is 5 calls per " - "minute and 500 per day, so you may need to pause between successive " - "commands. Note that retrieving prices for one stock consumes two " - "API calls." + "Alpha Vantage's standard API rate limit is 25 requests per day. " + "Note that retrieving prices for one stock consumes two API calls." ) def _stock_symbols_message(self): From 59574e91566d80f970a90009064cf967662d0043 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 14 Sep 2024 22:22:35 +0200 Subject: [PATCH 28/38] Fix yahoo source. --- src/pricehist/sources/yahoo.py | 103 +++----- tests/live.sh | 35 ++- tests/pricehist/sources/test_yahoo.py | 181 ++++--------- .../test_yahoo/ibm-date-with-nulls.csv | 4 - .../sources/test_yahoo/ibm-long-partial.csv | 11 - .../sources/test_yahoo/ibm-long-partial.json | 249 ++++++++++++++++++ .../sources/test_yahoo/tsla-recent.csv | 6 - .../sources/test_yahoo/tsla-recent.json | 126 +++++++++ .../sources/test_yahoo/tsla-spark.json | 77 ------ 9 files changed, 475 insertions(+), 317 deletions(-) delete mode 100644 tests/pricehist/sources/test_yahoo/ibm-date-with-nulls.csv delete mode 100644 tests/pricehist/sources/test_yahoo/ibm-long-partial.csv create mode 100644 tests/pricehist/sources/test_yahoo/ibm-long-partial.json delete mode 100644 tests/pricehist/sources/test_yahoo/tsla-recent.csv create mode 100644 tests/pricehist/sources/test_yahoo/tsla-recent.json delete mode 100644 tests/pricehist/sources/test_yahoo/tsla-spark.json diff --git a/src/pricehist/sources/yahoo.py b/src/pricehist/sources/yahoo.py index 6997301..ce5cf7d 100644 --- a/src/pricehist/sources/yahoo.py +++ b/src/pricehist/sources/yahoo.py @@ -1,4 +1,3 @@ -import csv import dataclasses import json import logging @@ -71,63 +70,36 @@ class Yahoo(BaseSource): series.base, series.quote, self, "Don't specify the quote currency." ) - quote, history = self._data(series) + data = self._data(series) + quote = data["chart"]["result"][0]["meta"]["currency"] + + timestamps = data["chart"]["result"][0]["timestamp"] + adjclose_data = data["chart"]["result"][0]["indicators"]["adjclose"][0] + rest_data = data["chart"]["result"][0]["indicators"]["quote"][0] + amounts = {**adjclose_data, **rest_data} prices = [ - Price(row["date"], amount) - for row in history - if (amount := self._amount(row, series.type)) + Price(ts, amount) + for i in range(len(timestamps)) + if (ts := datetime.fromtimestamp(timestamps[i]).strftime("%Y-%m-%d")) + <= series.end + if (amount := self._amount(amounts, series.type, i)) is not None ] return dataclasses.replace(series, quote=quote, prices=prices) - def _amount(self, row, type): - if type == "mid" and row["high"] != "null" and row["low"] != "null": - return sum([Decimal(row["high"]), Decimal(row["low"])]) / 2 - elif row[type] != "null": - return Decimal(row[type]) + def _amount(self, amounts, type, i): + if type == "mid" and amounts["high"] != "null" and amounts["low"] != "null": + return sum([Decimal(amounts["high"][i]), Decimal(amounts["low"][i])]) / 2 + elif amounts[type] != "null": + return Decimal(amounts[type][i]) else: return None - def _data(self, series) -> (dict, csv.DictReader): - base_url = "https://query1.finance.yahoo.com/v7/finance" + def _data(self, series) -> dict: + base_url = "https://query1.finance.yahoo.com/v8/finance/chart" headers = {"User-Agent": f"pricehist/{__version__}"} - - spark_url = f"{base_url}/spark" - spark_params = { - "symbols": series.base, - "range": "1d", - "interval": "1d", - "indicators": "close", - "includeTimestamps": "false", - "includePrePost": "false", - } - try: - spark_response = self.log_curl( - requests.get(spark_url, params=spark_params, headers=headers) - ) - except Exception as e: - raise exceptions.RequestError(str(e)) from e - - code = spark_response.status_code - text = spark_response.text - if code == 404 and "No data found for spark symbols" in text: - raise exceptions.InvalidPair( - series.base, series.quote, self, "Symbol not found." - ) - - try: - spark_response.raise_for_status() - except Exception as e: - raise exceptions.BadResponse(str(e)) from e - - try: - spark = json.loads(spark_response.content) - quote = spark["spark"]["result"][0]["response"][0]["meta"]["currency"] - except Exception as e: - raise exceptions.ResponseParsingError( - "The spark data couldn't be parsed. " - ) from e + url = f"{base_url}/{series.base}" start_ts = int( datetime.strptime(series.start, "%Y-%m-%d") @@ -142,24 +114,26 @@ class Yahoo(BaseSource): 24 * 60 * 60 ) # some symbols require padding on the end timestamp - history_url = f"{base_url}/download/{series.base}" - history_params = { + params = { + "symbol": series.base, "period1": start_ts, "period2": end_ts, "interval": "1d", - "events": "history", + "events": "capitalGain%7Cdiv%7Csplit", "includeAdjustedClose": "true", + "formatted": "true", + "userYfid": "true", + "lang": "en-US", + "region": "US", } try: - history_response = self.log_curl( - requests.get(history_url, params=history_params, headers=headers) - ) + response = self.log_curl(requests.get(url, params=params, headers=headers)) except Exception as e: raise exceptions.RequestError(str(e)) from e - code = history_response.status_code - text = history_response.text + code = response.status_code + text = response.text if code == 404 and "No data found, symbol may be delisted" in text: raise exceptions.InvalidPair( @@ -177,20 +151,15 @@ class Yahoo(BaseSource): ) try: - history_response.raise_for_status() + response.raise_for_status() except Exception as e: raise exceptions.BadResponse(str(e)) from e try: - history_lines = history_response.content.decode("utf-8").splitlines() - history_lines[0] = history_lines[0].lower().replace(" ", "") - history = csv.DictReader(history_lines, delimiter=",") + data = json.loads(response.content) except Exception as e: - raise exceptions.ResponseParsingError(str(e)) from e + raise exceptions.ResponseParsingError( + "The data couldn't be parsed. " + ) from e - if history_lines[0] != "date,open,high,low,close,adjclose,volume": - raise exceptions.ResponseParsingError("Unexpected CSV format") - - requested_history = [row for row in history if row["date"] <= series.end] - - return (quote, requested_history) + return data diff --git a/tests/live.sh b/tests/live.sh index dd5f626..fc24c08 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -116,21 +116,20 @@ date,base,quote,amount,source,type 2021-01-07,BTC,EUR,31208.49,coinbasepro,mid 2021-01-08,BTC,EUR,32019,coinbasepro,mid END -run_test "$name" "$cmd" "$expected" - -name="CoinDesk Bitcoin Price Index" -cmd="pricehist fetch coindesk BTC/EUR -s 2021-01-04 -e 2021-01-08" -read -r -d '' expected < 9 -def test_fetch_skips_dates_with_nulls(src, type, spark_ok, date_with_nulls_ok): - series = src.fetch(Series("IBM", "", type, "2021-01-05", "2021-01-07")) - assert series.prices[0] == Price("2021-01-05", Decimal("123.101204")) - assert series.prices[1] == Price("2021-01-07", Decimal("125.882545")) - assert len(series.prices) == 2 - - -def test_fetch_to_future(src, type, spark_ok, recent_ok): +def test_fetch_to_future(src, type, recent_ok): series = src.fetch(Series("TSLA", "", type, "2021-01-04", "2100-01-08")) assert len(series.prices) > 0 -def test_fetch_no_data_in_past(src, type, spark_ok, requests_mock): +def test_fetch_no_data_in_past(src, type, requests_mock): requests_mock.add( responses.GET, - history_url("TSLA"), + url("TSLA"), status=400, body=( "400 Bad Request: Data doesn't exist for " @@ -203,10 +177,10 @@ def test_fetch_no_data_in_past(src, type, spark_ok, requests_mock): assert "No data for the given interval" in str(e.value) -def test_fetch_no_data_in_future(src, type, spark_ok, requests_mock): +def test_fetch_no_data_in_future(src, type, requests_mock): requests_mock.add( responses.GET, - history_url("TSLA"), + url("TSLA"), status=400, body=( "400 Bad Request: Data doesn't exist for " @@ -218,10 +192,10 @@ def test_fetch_no_data_in_future(src, type, spark_ok, requests_mock): assert "No data for the given interval" in str(e.value) -def test_fetch_no_data_on_weekend(src, type, spark_ok, requests_mock): +def test_fetch_no_data_on_weekend(src, type, requests_mock): requests_mock.add( responses.GET, - history_url("TSLA"), + url("TSLA"), status=404, body="404 Not Found: Timestamp data missing.", ) @@ -233,30 +207,7 @@ def test_fetch_no_data_on_weekend(src, type, spark_ok, requests_mock): def test_fetch_bad_sym(src, type, requests_mock): requests_mock.add( responses.GET, - spark_url, - status=404, - body="""{ - "spark": { - "result": null, - "error": { - "code": "Not Found", - "description": "No data found for spark symbols" - } - } - }""", - ) - with pytest.raises(exceptions.InvalidPair) as e: - src.fetch(Series("NOTABASE", "", type, "2021-01-04", "2021-01-08")) - assert "Symbol not found" in str(e.value) - - -def test_fetch_bad_sym_history(src, type, spark_ok, requests_mock): - # In practice the spark history requests should succeed or fail together. - # This extra test ensures that a failure of the the history part is handled - # correctly even if the spark part succeeds. - requests_mock.add( - responses.GET, - history_url("NOTABASE"), + url("NOTABASE"), status=404, body="404 Not Found: No data found, symbol may be delisted", ) @@ -271,61 +222,23 @@ def test_fetch_giving_quote(src, type): assert "quote currency" in str(e.value) -def test_fetch_spark_network_issue(src, type, requests_mock): +def test_fetch_network_issue(src, type, requests_mock): body = requests.exceptions.ConnectionError("Network issue") - requests_mock.add(responses.GET, spark_url, body=body) + requests_mock.add(responses.GET, url("TSLA"), body=body) with pytest.raises(exceptions.RequestError) as e: src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) assert "Network issue" in str(e.value) -def test_fetch_spark_bad_status(src, type, requests_mock): - requests_mock.add(responses.GET, spark_url, status=500, body="Some other reason") +def test_fetch_bad_status(src, type, requests_mock): + requests_mock.add(responses.GET, url("TSLA"), status=500, body="Some other reason") with pytest.raises(exceptions.BadResponse) as e: src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) assert "Internal Server Error" in str(e.value) -def test_fetch_spark_parsing_error(src, type, requests_mock): - requests_mock.add(responses.GET, spark_url, body="NOT JSON") - with pytest.raises(exceptions.ResponseParsingError) as e: - src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) - assert "spark data couldn't be parsed" in str(e.value) - - -def test_fetch_spark_unexpected_json(src, type, requests_mock): - requests_mock.add(responses.GET, spark_url, body='{"notdata": []}') - with pytest.raises(exceptions.ResponseParsingError) as e: - src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) - assert "spark data couldn't be parsed" in str(e.value) - - -def test_fetch_history_network_issue(src, type, spark_ok, requests_mock): - body = requests.exceptions.ConnectionError("Network issue") - requests_mock.add(responses.GET, history_url("TSLA"), body=body) - with pytest.raises(exceptions.RequestError) as e: - src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) - assert "Network issue" in str(e.value) - - -def test_fetch_history_bad_status(src, type, spark_ok, requests_mock): - requests_mock.add( - responses.GET, history_url("TSLA"), status=500, body="Some other reason" - ) - with pytest.raises(exceptions.BadResponse) as e: - src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) - assert "Internal Server Error" in str(e.value) - - -def test_fetch_history_parsing_error(src, type, spark_ok, requests_mock): - requests_mock.add(responses.GET, history_url("TSLA"), body="") +def test_fetch_parsing_error(src, type, requests_mock): + requests_mock.add(responses.GET, url("TSLA"), body="") with pytest.raises(exceptions.ResponseParsingError) as e: src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) assert "error occurred while parsing data from the source" in str(e.value) - - -def test_fetch_history_unexpected_csv_format(src, type, spark_ok, requests_mock): - requests_mock.add(responses.GET, history_url("TSLA"), body="BAD HEADER\nBAD DATA") - with pytest.raises(exceptions.ResponseParsingError) as e: - src.fetch(Series("TSLA", "", type, "2021-01-04", "2021-01-08")) - assert "Unexpected CSV format" in str(e.value) diff --git a/tests/pricehist/sources/test_yahoo/ibm-date-with-nulls.csv b/tests/pricehist/sources/test_yahoo/ibm-date-with-nulls.csv deleted file mode 100644 index 601b395..0000000 --- a/tests/pricehist/sources/test_yahoo/ibm-date-with-nulls.csv +++ /dev/null @@ -1,4 +0,0 @@ -Date,Open,High,Low,Close,Adj Close,Volume -2021-01-05,125.010002,126.680000,124.610001,126.139999,123.101204,6114600 -2021-01-06,null,null,null,null,null,null -2021-01-07,130.039993,130.460007,128.259995,128.990005,125.882545,4507400 diff --git a/tests/pricehist/sources/test_yahoo/ibm-long-partial.csv b/tests/pricehist/sources/test_yahoo/ibm-long-partial.csv deleted file mode 100644 index 98149ad..0000000 --- a/tests/pricehist/sources/test_yahoo/ibm-long-partial.csv +++ /dev/null @@ -1,11 +0,0 @@ -Date,Open,High,Low,Close,Adj Close,Volume -1962-01-02,7.713333,7.713333,7.626667,7.626667,1.837710,390000 -1962-01-03,7.626667,7.693333,7.626667,7.693333,1.853774,292500 -1962-01-04,7.693333,7.693333,7.613333,7.616667,1.835299,262500 -1962-01-05,7.606667,7.606667,7.453333,7.466667,1.799155,367500 -1962-01-08,7.460000,7.460000,7.266667,7.326667,1.765422,547500 -2021-01-04,125.849998,125.919998,123.040001,123.940002,120.954201,5179200 -2021-01-05,125.010002,126.680000,124.610001,126.139999,123.101204,6114600 -2021-01-06,126.900002,131.880005,126.720001,129.289993,126.175316,7956700 -2021-01-07,130.039993,130.460007,128.259995,128.990005,125.882545,4507400 -2021-01-08,128.570007,129.320007,126.980003,128.529999,125.433624,4676200 diff --git a/tests/pricehist/sources/test_yahoo/ibm-long-partial.json b/tests/pricehist/sources/test_yahoo/ibm-long-partial.json new file mode 100644 index 0000000..df98efa --- /dev/null +++ b/tests/pricehist/sources/test_yahoo/ibm-long-partial.json @@ -0,0 +1,249 @@ +{ + "chart": { + "result": [ + { + "meta": { + "currency": "USD", + "symbol": "IBM", + "exchangeName": "NYQ", + "fullExchangeName": "NYSE", + "instrumentType": "EQUITY", + "firstTradeDate": -252322200, + "regularMarketTime": 1726257602, + "hasPrePostMarketData": true, + "gmtoffset": -14400, + "timezone": "EDT", + "exchangeTimezoneName": "America/New_York", + "regularMarketPrice": 214.79, + "fiftyTwoWeekHigh": 216.08, + "fiftyTwoWeekLow": 212.13, + "regularMarketDayHigh": 216.08, + "regularMarketDayLow": 212.13, + "regularMarketVolume": 4553547, + "longName": "International Business Machines Corporation", + "shortName": "International Business Machines", + "chartPreviousClose": 7.291, + "priceHint": 2, + "currentTradingPeriod": { + "pre": { + "timezone": "EDT", + "end": 1726234200, + "start": 1726214400, + "gmtoffset": -14400 + }, + "regular": { + "timezone": "EDT", + "end": 1726257600, + "start": 1726234200, + "gmtoffset": -14400 + }, + "post": { + "timezone": "EDT", + "end": 1726272000, + "start": 1726257600, + "gmtoffset": -14400 + } + }, + "dataGranularity": "1d", + "range": "", + "validRanges": [ + "1d", + "5d", + "1mo", + "3mo", + "6mo", + "1y", + "2y", + "5y", + "10y", + "ytd", + "max" + ] + }, + "timestamp": [ + -252322200, + -252235800, + -252149400, + -252063000, + -251803800, + 1609770600, + 1609857000, + 1609943400, + 1610029800, + 1610116200 + ], + "events": { + "dividends": { + "-249298200": { + "amount": 0.000956, + "date": -249298200 + }, + "-241439400": { + "amount": 0.000956, + "date": -241439400 + }, + "-233577000": { + "amount": 0.000956, + "date": -233577000 + }, + "-225797400": { + "amount": 0.000956, + "date": -225797400 + }, + "-217848600": { + "amount": 0.001275, + "date": -217848600 + }, + "1573137000": { + "amount": 1.548757, + "date": 1573137000 + }, + "1581085800": { + "amount": 1.548757, + "date": 1581085800 + }, + "1588858200": { + "amount": 1.558317, + "date": 1588858200 + }, + "1596807000": { + "amount": 1.558317, + "date": 1596807000 + }, + "1604932200": { + "amount": 1.558317, + "date": 1604932200 + } + }, + "splits": { + "-177417000": { + "date": -177417000, + "numerator": 5.0, + "denominator": 4.0, + "splitRatio": "5:4" + }, + "-114345000": { + "date": -114345000, + "numerator": 3.0, + "denominator": 2.0, + "splitRatio": "3:2" + }, + "-53343000": { + "date": -53343000, + "numerator": 2.0, + "denominator": 1.0, + "splitRatio": "2:1" + }, + "107530200": { + "date": 107530200, + "numerator": 5.0, + "denominator": 4.0, + "splitRatio": "5:4" + }, + "297091800": { + "date": 297091800, + "numerator": 4.0, + "denominator": 1.0, + "splitRatio": "4:1" + }, + "864826200": { + "date": 864826200, + "numerator": 2.0, + "denominator": 1.0, + "splitRatio": "2:1" + }, + "927811800": { + "date": 927811800, + "numerator": 2.0, + "denominator": 1.0, + "splitRatio": "2:1" + } + } + }, + "indicators": { + "quote": [ + { + "close": [ + 7.2912678718566895, + 7.3550028800964355, + 7.281707763671875, + 7.138305187225342, + 7.00446081161499, + 118.48948669433594, + 120.59273529052734, + 123.60420989990234, + 123.31739807128906, + 122.87763214111328 + ], + "low": [ + 7.2912678718566895, + 7.2912678718566895, + 7.2785210609436035, + 7.125557899475098, + 6.9471001625061035, + 117.62906646728516, + 119.13002014160156, + 121.14722442626953, + 122.61949920654297, + 121.39579010009766 + ], + "open": [ + 7.374124050140381, + 7.2912678718566895, + 7.3550028800964355, + 7.272148132324219, + 7.131930828094482, + 120.31549072265625, + 119.5124282836914, + 121.3193130493164, + 124.32122039794922, + 122.9158706665039 + ], + "high": [ + 7.374124050140381, + 7.3550028800964355, + 7.3550028800964355, + 7.272148132324219, + 7.131930828094482, + 120.38240814208984, + 121.1089859008789, + 126.08030700683594, + 124.7227554321289, + 123.63288879394531 + ], + "volume": [ + 407940, + 305955, + 274575, + 384405, + 572685, + 5417443, + 6395872, + 8322708, + 4714740, + 4891305 + ] + } + ], + "adjclose": [ + { + "adjclose": [ + 1.5133211612701416, + 1.5265485048294067, + 1.5113375186920166, + 1.4815733432769775, + 1.4537923336029053, + 99.60364532470703, + 101.37164306640625, + 103.90313720703125, + 103.66202545166016, + 103.29237365722656 + ] + } + ] + } + } + ], + "error": null + } +} diff --git a/tests/pricehist/sources/test_yahoo/tsla-recent.csv b/tests/pricehist/sources/test_yahoo/tsla-recent.csv deleted file mode 100644 index 48b5692..0000000 --- a/tests/pricehist/sources/test_yahoo/tsla-recent.csv +++ /dev/null @@ -1,6 +0,0 @@ -Date,Open,High,Low,Close,Adj Close,Volume -2021-01-04,719.460022,744.489990,717.190002,729.770020,729.770020,48638200 -2021-01-05,723.659973,740.840027,719.200012,735.109985,735.109985,32245200 -2021-01-06,758.489990,774.000000,749.099976,755.979980,755.979980,44700000 -2021-01-07,777.630005,816.989990,775.200012,816.039978,816.039978,51498900 -2021-01-08,856.000000,884.489990,838.390015,880.020020,880.020020,75055500 \ No newline at end of file diff --git a/tests/pricehist/sources/test_yahoo/tsla-recent.json b/tests/pricehist/sources/test_yahoo/tsla-recent.json new file mode 100644 index 0000000..3f35daa --- /dev/null +++ b/tests/pricehist/sources/test_yahoo/tsla-recent.json @@ -0,0 +1,126 @@ +{ + "chart": { + "result": [ + { + "meta": { + "currency": "USD", + "symbol": "TSLA", + "exchangeName": "NMS", + "fullExchangeName": "NasdaqGS", + "instrumentType": "EQUITY", + "firstTradeDate": 1277818200, + "regularMarketTime": 1726257600, + "hasPrePostMarketData": true, + "gmtoffset": -14400, + "timezone": "EDT", + "exchangeTimezoneName": "America/New_York", + "regularMarketPrice": 230.29, + "fiftyTwoWeekHigh": 232.664, + "fiftyTwoWeekLow": 226.32, + "regularMarketDayHigh": 232.664, + "regularMarketDayLow": 226.32, + "regularMarketVolume": 59096538, + "longName": "Tesla, Inc.", + "shortName": "Tesla, Inc.", + "chartPreviousClose": 235.223, + "priceHint": 2, + "currentTradingPeriod": { + "pre": { + "timezone": "EDT", + "start": 1726214400, + "end": 1726234200, + "gmtoffset": -14400 + }, + "regular": { + "timezone": "EDT", + "start": 1726234200, + "end": 1726257600, + "gmtoffset": -14400 + }, + "post": { + "timezone": "EDT", + "start": 1726257600, + "end": 1726272000, + "gmtoffset": -14400 + } + }, + "dataGranularity": "1d", + "range": "", + "validRanges": [ + "1d", + "5d", + "1mo", + "3mo", + "6mo", + "1y", + "2y", + "5y", + "10y", + "ytd", + "max" + ] + }, + "timestamp": [ + 1609770600, + 1609857000, + 1609943400, + 1610029800, + 1610116200 + ], + "indicators": { + "quote": [ + { + "open": [ + 239.82000732421875, + 241.22000122070312, + 252.8300018310547, + 259.2099914550781, + 285.3333435058594 + ], + "close": [ + 243.2566680908203, + 245.0366668701172, + 251.9933319091797, + 272.0133361816406, + 293.3399963378906 + ], + "high": [ + 248.163330078125, + 246.94667053222656, + 258.0, + 272.3299865722656, + 294.8299865722656 + ], + "low": [ + 239.06333923339844, + 239.73333740234375, + 249.6999969482422, + 258.3999938964844, + 279.46331787109375 + ], + "volume": [ + 145914600, + 96735600, + 134100000, + 154496700, + 225166500 + ] + } + ], + "adjclose": [ + { + "adjclose": [ + 243.2566680908203, + 245.0366668701172, + 251.9933319091797, + 272.0133361816406, + 293.3399963378906 + ] + } + ] + } + } + ], + "error": null + } +} diff --git a/tests/pricehist/sources/test_yahoo/tsla-spark.json b/tests/pricehist/sources/test_yahoo/tsla-spark.json deleted file mode 100644 index 53e7585..0000000 --- a/tests/pricehist/sources/test_yahoo/tsla-spark.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "spark": { - "result": [ - { - "symbol": "TSLA", - "response": [ - { - "meta": { - "currency": "USD", - "symbol": "TSLA", - "exchangeName": "NMS", - "instrumentType": "EQUITY", - "firstTradeDate": 1277818200, - "regularMarketTime": 1626465603, - "gmtoffset": -14400, - "timezone": "EDT", - "exchangeTimezoneName": "America/New_York", - "regularMarketPrice": 644.22, - "chartPreviousClose": 650.6, - "priceHint": 2, - "currentTradingPeriod": { - "pre": { - "timezone": "EDT", - "start": 1626422400, - "end": 1626442200, - "gmtoffset": -14400 - }, - "regular": { - "timezone": "EDT", - "start": 1626442200, - "end": 1626465600, - "gmtoffset": -14400 - }, - "post": { - "timezone": "EDT", - "start": 1626465600, - "end": 1626480000, - "gmtoffset": -14400 - } - }, - "dataGranularity": "1d", - "range": "1d", - "validRanges": [ - "1d", - "5d", - "1mo", - "3mo", - "6mo", - "1y", - "2y", - "5y", - "10y", - "ytd", - "max" - ] - }, - "timestamp": [ - 1626442200, - 1626465603 - ], - "indicators": { - "quote": [ - { - "close": [ - 644.22, - 644.22 - ] - } - ] - } - } - ] - } - ], - "error": null - } -} From 5e75759b0fa8c14bc7ab88e5fd7d93e0cf3b08a2 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 14 Sep 2024 22:24:46 +0200 Subject: [PATCH 29/38] Version 1.4.10. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d1a7ce1..d5fc989 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.9" +version = "1.4.10" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index 1ad354e..738cf25 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.9" +__version__ = "1.4.10" From b6f4c175303e6470b24ded7a4112b81abc40ed7c Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 14 Sep 2024 22:49:45 +0200 Subject: [PATCH 30/38] Skip coindesk live test. --- tests/live.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/live.sh b/tests/live.sh index fc24c08..cb2dbeb 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -128,7 +128,7 @@ date,base,quote,amount,source,type 2021-01-07,BTC,USD,39713.5079,coindeskbpi,close 2021-01-08,BTC,USD,40519.4486,coindeskbpi,close END -run_test "$name" "$cmd" "$expected" +skip_test "$name" "$cmd" "$expected" name="CoinMarketCap" cmd="pricehist fetch coinmarketcap BTC/EUR -s 2021-01-04 -e 2021-01-08" From ee8ca0573d73765f298a83961eb2eb1583173f48 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 12:46:43 +0200 Subject: [PATCH 31/38] yahoo: add back null handling, improve timestamp handling. Thanks @arkn98! --- src/pricehist/sources/yahoo.py | 15 +++++++++++---- tests/pricehist/sources/test_yahoo.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/pricehist/sources/yahoo.py b/src/pricehist/sources/yahoo.py index ce5cf7d..65857bb 100644 --- a/src/pricehist/sources/yahoo.py +++ b/src/pricehist/sources/yahoo.py @@ -72,6 +72,7 @@ class Yahoo(BaseSource): data = self._data(series) quote = data["chart"]["result"][0]["meta"]["currency"] + offset = data["chart"]["result"][0]["meta"]["gmtoffset"] timestamps = data["chart"]["result"][0]["timestamp"] adjclose_data = data["chart"]["result"][0]["indicators"]["adjclose"][0] @@ -79,19 +80,25 @@ class Yahoo(BaseSource): amounts = {**adjclose_data, **rest_data} prices = [ - Price(ts, amount) + Price(date, amount) for i in range(len(timestamps)) - if (ts := datetime.fromtimestamp(timestamps[i]).strftime("%Y-%m-%d")) - <= series.end + if (date := self._date_from_ts(timestamps[i], offset)) <= series.end if (amount := self._amount(amounts, series.type, i)) is not None ] return dataclasses.replace(series, quote=quote, prices=prices) + def _date_from_ts(self, ts, offset) -> str: + return ( + datetime.fromtimestamp(ts - offset) + .replace(tzinfo=timezone.utc) + .strftime("%Y-%m-%d") + ) + def _amount(self, amounts, type, i): if type == "mid" and amounts["high"] != "null" and amounts["low"] != "null": return sum([Decimal(amounts["high"][i]), Decimal(amounts["low"][i])]) / 2 - elif amounts[type] != "null": + elif amounts[type] != "null" and amounts[type][i] is not None: return Decimal(amounts[type][i]) else: return None diff --git a/tests/pricehist/sources/test_yahoo.py b/tests/pricehist/sources/test_yahoo.py index 7e8d055..d490d86 100644 --- a/tests/pricehist/sources/test_yahoo.py +++ b/tests/pricehist/sources/test_yahoo.py @@ -54,6 +54,13 @@ def long_ok(requests_mock): yield requests_mock +@pytest.fixture +def with_null_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "inrx-with-null.json").read_text() + requests_mock.add(responses.GET, url("INR=X"), body=json, status=200) + yield requests_mock + + def test_normalizesymbol(src): assert src.normalizesymbol("tsla") == "TSLA" @@ -157,6 +164,13 @@ def test_fetch_from_before_start(src, type, long_ok): assert len(series.prices) > 9 +def test_fetch_skips_dates_with_nulls(src, type, with_null_ok): + series = src.fetch(Series("INR=X", "", type, "2017-07-10", "2017-07-12")) + assert series.prices[0] == Price("2017-07-10", Decimal("64.61170196533203125")) + assert series.prices[1] == Price("2017-07-12", Decimal("64.52559661865234375")) + assert len(series.prices) == 2 + + def test_fetch_to_future(src, type, recent_ok): series = src.fetch(Series("TSLA", "", type, "2021-01-04", "2100-01-08")) assert len(series.prices) > 0 From 77b2776e55d0c9a6e4265bff94665f1838cba7c0 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 12:59:39 +0200 Subject: [PATCH 32/38] yahoo: More graceful handling of responses with meta but no timestamps. --- src/pricehist/sources/yahoo.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pricehist/sources/yahoo.py b/src/pricehist/sources/yahoo.py index 65857bb..169bf71 100644 --- a/src/pricehist/sources/yahoo.py +++ b/src/pricehist/sources/yahoo.py @@ -146,11 +146,10 @@ class Yahoo(BaseSource): raise exceptions.InvalidPair( series.base, series.quote, self, "Symbol not found." ) - if code == 400 and "Data doesn't exist" in text: + elif code == 400 and "Data doesn't exist" in text: raise exceptions.BadResponse( "No data for the given interval. Try requesting a larger interval." ) - elif code == 404 and "Timestamp data missing" in text: raise exceptions.BadResponse( "Data missing. The given interval may be for a gap in the data " @@ -169,4 +168,10 @@ class Yahoo(BaseSource): "The data couldn't be parsed. " ) from e + if "timestamp" not in data["chart"]["result"][0]: + raise exceptions.BadResponse( + "No data for the given interval. " + "There may be a problem with the symbol or the interval." + ) + return data From 1164724ffb8cb2b614af06d2a1d4634cf0a2c886 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 13:01:11 +0200 Subject: [PATCH 33/38] Version 1.4.11. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d5fc989..98bb405 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.10" +version = "1.4.11" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index 738cf25..e42b3cf 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.10" +__version__ = "1.4.11" From c78154df3a265da9c26a149d78bff1989fe046fd Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 13:07:50 +0200 Subject: [PATCH 34/38] Add missing file. --- .../sources/test_yahoo/inrx-with-null.json | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/pricehist/sources/test_yahoo/inrx-with-null.json diff --git a/tests/pricehist/sources/test_yahoo/inrx-with-null.json b/tests/pricehist/sources/test_yahoo/inrx-with-null.json new file mode 100644 index 0000000..5ae762e --- /dev/null +++ b/tests/pricehist/sources/test_yahoo/inrx-with-null.json @@ -0,0 +1,119 @@ +{ + "chart": { + "result": [ + { + "meta": { + "currency": "INR", + "symbol": "INR=X", + "exchangeName": "CCY", + "fullExchangeName": "CCY", + "instrumentType": "CURRENCY", + "firstTradeDate": 1070236800, + "regularMarketTime": 1726284616, + "hasPrePostMarketData": false, + "gmtoffset": 3600, + "timezone": "BST", + "exchangeTimezoneName": "Europe/London", + "regularMarketPrice": 83.89, + "fiftyTwoWeekHigh": 83.89, + "fiftyTwoWeekLow": 83.89, + "regularMarketDayHigh": 83.89, + "regularMarketDayLow": 83.89, + "regularMarketVolume": 0, + "longName": "USD/INR", + "shortName": "USD/INR", + "chartPreviousClose": 64.6117, + "priceHint": 4, + "currentTradingPeriod": { + "pre": { + "timezone": "BST", + "start": 1726182000, + "end": 1726182000, + "gmtoffset": 3600 + }, + "regular": { + "timezone": "BST", + "start": 1726182000, + "end": 1726268340, + "gmtoffset": 3600 + }, + "post": { + "timezone": "BST", + "start": 1726268340, + "end": 1726268340, + "gmtoffset": 3600 + } + }, + "dataGranularity": "1d", + "range": "", + "validRanges": [ + "1d", + "5d", + "1mo", + "3mo", + "6mo", + "1y", + "2y", + "5y", + "10y", + "ytd", + "max" + ] + }, + "timestamp": [ + 1499641200, + 1499727600, + 1499814000, + 1499900400 + ], + "indicators": { + "quote": [ + { + "open": [ + 64.6155014038086, + null, + 64.55549621582031, + 64.46800231933594 + ], + "volume": [ + 0, + null, + 0, + 0 + ], + "low": [ + 64.41000366210938, + null, + 64.3499984741211, + 64.33999633789062 + ], + "close": [ + 64.61170196533203, + null, + 64.52559661865234, + 64.36499786376953 + ], + "high": [ + 64.6155014038086, + null, + 64.56999969482422, + 64.48419952392578 + ] + } + ], + "adjclose": [ + { + "adjclose": [ + 64.61170196533203, + null, + 64.52559661865234, + 64.36499786376953 + ] + } + ] + } + } + ], + "error": null + } +} From dffe6f8e89678751478382b8549b520f43296e9e Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 13:15:18 +0200 Subject: [PATCH 35/38] Timezone handling tweak. --- src/pricehist/sources/yahoo.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pricehist/sources/yahoo.py b/src/pricehist/sources/yahoo.py index 169bf71..81dd0f7 100644 --- a/src/pricehist/sources/yahoo.py +++ b/src/pricehist/sources/yahoo.py @@ -89,11 +89,7 @@ class Yahoo(BaseSource): return dataclasses.replace(series, quote=quote, prices=prices) def _date_from_ts(self, ts, offset) -> str: - return ( - datetime.fromtimestamp(ts - offset) - .replace(tzinfo=timezone.utc) - .strftime("%Y-%m-%d") - ) + return datetime.fromtimestamp(ts - offset).strftime("%Y-%m-%d") def _amount(self, amounts, type, i): if type == "mid" and amounts["high"] != "null" and amounts["low"] != "null": From 53f39a26ef9e28565826456e65d119b6f4209d6a Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 11:35:56 +0000 Subject: [PATCH 36/38] More time correction. --- src/pricehist/sources/yahoo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pricehist/sources/yahoo.py b/src/pricehist/sources/yahoo.py index 81dd0f7..25d92fe 100644 --- a/src/pricehist/sources/yahoo.py +++ b/src/pricehist/sources/yahoo.py @@ -82,14 +82,14 @@ class Yahoo(BaseSource): prices = [ Price(date, amount) for i in range(len(timestamps)) - if (date := self._date_from_ts(timestamps[i], offset)) <= series.end + if (date := self._ts_to_date(timestamps[i] + offset)) <= series.end if (amount := self._amount(amounts, series.type, i)) is not None ] return dataclasses.replace(series, quote=quote, prices=prices) - def _date_from_ts(self, ts, offset) -> str: - return datetime.fromtimestamp(ts - offset).strftime("%Y-%m-%d") + def _ts_to_date(self, ts) -> str: + return datetime.fromtimestamp(ts, tz=timezone.utc).date().isoformat() def _amount(self, amounts, type, i): if type == "mid" and amounts["high"] != "null" and amounts["low"] != "null": From ab507b189cdb30f4df7ead380bc0151740f249b3 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sun, 15 Sep 2024 12:16:36 +0000 Subject: [PATCH 37/38] Update live test. --- tests/live.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/live.sh b/tests/live.sh index cb2dbeb..157cdad 100755 --- a/tests/live.sh +++ b/tests/live.sh @@ -75,10 +75,10 @@ name="Alpha Vantage physical currency" cmd="pricehist fetch alphavantage AUD/EUR -s 2021-01-11 -e 2021-01-14" read -r -d '' expected < Date: Sun, 15 Sep 2024 12:17:10 +0000 Subject: [PATCH 38/38] Version 1.4.12. --- pyproject.toml | 2 +- src/pricehist/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 98bb405..5d555aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pricehist" -version = "1.4.11" +version = "1.4.12" description = "Fetch and format historical price data" authors = ["Chris Berkhout "] license = "MIT" diff --git a/src/pricehist/__init__.py b/src/pricehist/__init__.py index e42b3cf..2736991 100644 --- a/src/pricehist/__init__.py +++ b/src/pricehist/__init__.py @@ -1 +1 @@ -__version__ = "1.4.11" +__version__ = "1.4.12"