lib-x17-fintech 2.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
- lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
- lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
- lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
- lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
- xfintech/__init__.py +0 -0
- xfintech/connect/__init__.py +18 -0
- xfintech/connect/artifact/__init__.py +5 -0
- xfintech/connect/artifact/artifact.py +168 -0
- xfintech/connect/artifact/tests/__init__.py +3 -0
- xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
- xfintech/connect/common/__init__.py +12 -0
- xfintech/connect/common/connect.py +49 -0
- xfintech/connect/common/connectref.py +119 -0
- xfintech/connect/common/error.py +62 -0
- xfintech/connect/common/tests/__init__.py +1 -0
- xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
- xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
- xfintech/connect/common/tests/test_class_errors_all.py +524 -0
- xfintech/connect/instance/__init__.py +7 -0
- xfintech/connect/instance/macos.py +121 -0
- xfintech/connect/instance/s3.py +176 -0
- xfintech/connect/instance/tests/__init__.py +1 -0
- xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
- xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
- xfintech/data/__init__.py +20 -0
- xfintech/data/common/__init__.py +15 -0
- xfintech/data/common/cache.py +186 -0
- xfintech/data/common/coolant.py +171 -0
- xfintech/data/common/metric.py +138 -0
- xfintech/data/common/paginate.py +132 -0
- xfintech/data/common/params.py +162 -0
- xfintech/data/common/retry.py +201 -0
- xfintech/data/common/tests/__init__.py +1 -0
- xfintech/data/common/tests/test_class_cache_all.py +681 -0
- xfintech/data/common/tests/test_class_coolant_all.py +534 -0
- xfintech/data/common/tests/test_class_metric_all.py +705 -0
- xfintech/data/common/tests/test_class_paginate_all.py +508 -0
- xfintech/data/common/tests/test_class_params_all.py +891 -0
- xfintech/data/common/tests/test_class_retry_all.py +714 -0
- xfintech/data/job/__init__.py +17 -0
- xfintech/data/job/errors.py +112 -0
- xfintech/data/job/house.py +156 -0
- xfintech/data/job/job.py +247 -0
- xfintech/data/job/joblike.py +47 -0
- xfintech/data/job/tests/__init__.py +1 -0
- xfintech/data/job/tests/test_class_errors_all.py +275 -0
- xfintech/data/job/tests/test_class_house_all.py +801 -0
- xfintech/data/job/tests/test_class_job_all.py +684 -0
- xfintech/data/job/tests/test_class_joblike_all.py +482 -0
- xfintech/data/relay/__init__.py +7 -0
- xfintech/data/relay/client.py +114 -0
- xfintech/data/relay/clientlike.py +45 -0
- xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
- xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
- xfintech/data/source/__init__.py +7 -0
- xfintech/data/source/baostock/__init__.py +21 -0
- xfintech/data/source/baostock/job/__init__.py +5 -0
- xfintech/data/source/baostock/job/job.py +217 -0
- xfintech/data/source/baostock/job/tests/__init__.py +0 -0
- xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
- xfintech/data/source/baostock/session/__init__.py +8 -0
- xfintech/data/source/baostock/session/relay.py +223 -0
- xfintech/data/source/baostock/session/session.py +241 -0
- xfintech/data/source/baostock/session/tests/__init__.py +0 -0
- xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
- xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
- xfintech/data/source/baostock/stock/__init__.py +0 -0
- xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
- xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
- xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
- xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
- xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
- xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
- xfintech/data/source/baostock/stock/stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/stock/stock.py +149 -0
- xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
- xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
- xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
- xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
- xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
- xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
- xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
- xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
- xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
- xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
- xfintech/data/source/tushare/__init__.py +61 -0
- xfintech/data/source/tushare/job/__init__.py +5 -0
- xfintech/data/source/tushare/job/job.py +257 -0
- xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
- xfintech/data/source/tushare/session/__init__.py +5 -0
- xfintech/data/source/tushare/session/relay.py +231 -0
- xfintech/data/source/tushare/session/session.py +239 -0
- xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
- xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
- xfintech/data/source/tushare/stock/__init__.py +55 -0
- xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
- xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
- xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
- xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
- xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
- xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
- xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
- xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
- xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
- xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
- xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
- xfintech/data/source/tushare/stock/company/__init__.py +19 -0
- xfintech/data/source/tushare/stock/company/company.py +188 -0
- xfintech/data/source/tushare/stock/company/constant.py +92 -0
- xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
- xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
- xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
- xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
- xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
- xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
- xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
- xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
- xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
- xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
- xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
- xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
- xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
- xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
- xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
- xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
- xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
- xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
- xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
- xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
- xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stock/constant.py +105 -0
- xfintech/data/source/tushare/stock/stock/stock.py +193 -0
- xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
- xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
- xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
- xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
- xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
- xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
- xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
- xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
- xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
- xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
- xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
- xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
- xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
- xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
- xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
- xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
- xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
- xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
- xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
- xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
- xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
- xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
- xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
- xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
- xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
- xfintech/fabric/__init__.py +18 -0
- xfintech/fabric/column/__init__.py +7 -0
- xfintech/fabric/column/info.py +202 -0
- xfintech/fabric/column/kind.py +102 -0
- xfintech/fabric/column/tests/__init__.py +0 -0
- xfintech/fabric/column/tests/test_class_info_all.py +207 -0
- xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
- xfintech/fabric/table/__init__.py +5 -0
- xfintech/fabric/table/info.py +263 -0
- xfintech/fabric/table/tests/__init__.py +0 -0
- xfintech/fabric/table/tests/test_class_info_all.py +547 -0
- xfintech/serde/__init__.py +35 -0
- xfintech/serde/common/__init__.py +9 -0
- xfintech/serde/common/dataformat.py +78 -0
- xfintech/serde/common/deserialiserlike.py +38 -0
- xfintech/serde/common/error.py +182 -0
- xfintech/serde/common/serialiserlike.py +38 -0
- xfintech/serde/common/tests/__init__.py +1 -0
- xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
- xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
- xfintech/serde/common/tests/test_class_errors_all.py +518 -0
- xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
- xfintech/serde/deserialiser/__init__.py +7 -0
- xfintech/serde/deserialiser/pandas.py +113 -0
- xfintech/serde/deserialiser/python.py +68 -0
- xfintech/serde/deserialiser/tests/__init__.py +1 -0
- xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
- xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
- xfintech/serde/serialiser/__init__.py +7 -0
- xfintech/serde/serialiser/pandas.py +116 -0
- xfintech/serde/serialiser/python.py +71 -0
- xfintech/serde/serialiser/tests/__init__.py +1 -0
- xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
- xfintech/serde/serialiser/tests/test_class_pythonserialiser_all.py +508 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import backoff
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from xfintech.data.common.retry import Retry
|
|
7
|
+
|
|
8
|
+
# ==================== Retry Initialization Tests ====================
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_retry_init_defaults():
|
|
12
|
+
"""Test Retry initialization with default values"""
|
|
13
|
+
retry = Retry()
|
|
14
|
+
|
|
15
|
+
assert retry.retry == 0
|
|
16
|
+
assert retry.wait == 0
|
|
17
|
+
assert retry.rate == 1.0
|
|
18
|
+
assert retry.exceptions == (Exception,)
|
|
19
|
+
assert retry.jitter is True
|
|
20
|
+
assert retry.jitter_fn is not None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_retry_init_custom_values():
|
|
24
|
+
"""Test Retry initialization with custom values"""
|
|
25
|
+
retry = Retry(retry=5, wait=2.0, rate=1.5, jitter=False)
|
|
26
|
+
|
|
27
|
+
assert retry.retry == 5
|
|
28
|
+
assert retry.wait == 2.0
|
|
29
|
+
assert retry.rate == 1.5
|
|
30
|
+
assert retry.jitter is False
|
|
31
|
+
assert retry.jitter_fn is None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_retry_init_custom_exceptions():
|
|
35
|
+
"""Test Retry initialization with custom exceptions"""
|
|
36
|
+
retry = Retry(exceptions=[ValueError, TypeError])
|
|
37
|
+
|
|
38
|
+
assert retry.exceptions == (ValueError, TypeError)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_retry_init_single_exception():
|
|
42
|
+
"""Test Retry initialization with single exception"""
|
|
43
|
+
retry = Retry(exceptions=[ConnectionError])
|
|
44
|
+
|
|
45
|
+
assert retry.exceptions == (ConnectionError,)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_retry_init_none_exceptions():
|
|
49
|
+
"""Test Retry initialization with None exceptions defaults to Exception"""
|
|
50
|
+
retry = Retry(exceptions=None)
|
|
51
|
+
|
|
52
|
+
assert retry.exceptions == (Exception,)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ==================== Jitter Function Tests ====================
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_retry_jitter_enabled():
|
|
59
|
+
"""Test jitter_fn is set when jitter is True"""
|
|
60
|
+
retry = Retry(jitter=True)
|
|
61
|
+
|
|
62
|
+
assert retry.jitter_fn is not None
|
|
63
|
+
assert retry.jitter_fn == backoff.full_jitter
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_retry_jitter_disabled():
|
|
67
|
+
"""Test jitter_fn is None when jitter is False"""
|
|
68
|
+
retry = Retry(jitter=False)
|
|
69
|
+
|
|
70
|
+
assert retry.jitter_fn is None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_retry_resolve_jitter_fn():
|
|
74
|
+
"""Test _resolve_jitter_fn method"""
|
|
75
|
+
retry = Retry(jitter=True)
|
|
76
|
+
jitter_fn = retry._resolve_jitter_fn()
|
|
77
|
+
|
|
78
|
+
assert jitter_fn is not None
|
|
79
|
+
assert callable(jitter_fn)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ==================== No Retry Tests ====================
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_retry_zero_returns_original():
|
|
86
|
+
"""Test retry=0 returns original function without decoration"""
|
|
87
|
+
retry = Retry(retry=0)
|
|
88
|
+
|
|
89
|
+
@retry
|
|
90
|
+
def sample_func():
|
|
91
|
+
return "result"
|
|
92
|
+
|
|
93
|
+
assert sample_func() == "result"
|
|
94
|
+
# Function should be the original, not wrapped
|
|
95
|
+
assert sample_func.__name__ == "sample_func"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_retry_negative_returns_original():
|
|
99
|
+
"""Test negative retry returns original function"""
|
|
100
|
+
retry = Retry(retry=-1)
|
|
101
|
+
|
|
102
|
+
call_count = 0
|
|
103
|
+
|
|
104
|
+
@retry
|
|
105
|
+
def sample_func():
|
|
106
|
+
nonlocal call_count
|
|
107
|
+
call_count += 1
|
|
108
|
+
raise ValueError("Error")
|
|
109
|
+
|
|
110
|
+
with pytest.raises(ValueError):
|
|
111
|
+
sample_func()
|
|
112
|
+
|
|
113
|
+
# Should fail immediately without retry
|
|
114
|
+
assert call_count == 1
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ==================== Constant Interval Tests ====================
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_retry_constant_interval():
|
|
121
|
+
"""Test retry with constant interval (rate=1.0)"""
|
|
122
|
+
retry = Retry(retry=3, wait=0.01, rate=1.0, jitter=False)
|
|
123
|
+
|
|
124
|
+
call_count = 0
|
|
125
|
+
|
|
126
|
+
@retry
|
|
127
|
+
def failing_func():
|
|
128
|
+
nonlocal call_count
|
|
129
|
+
call_count += 1
|
|
130
|
+
if call_count < 3:
|
|
131
|
+
raise ValueError("Failed")
|
|
132
|
+
return "success"
|
|
133
|
+
|
|
134
|
+
result = failing_func()
|
|
135
|
+
|
|
136
|
+
assert result == "success"
|
|
137
|
+
assert call_count == 3
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_retry_constant_interval_all_fail():
|
|
141
|
+
"""Test retry with constant interval when all attempts fail"""
|
|
142
|
+
retry = Retry(retry=3, wait=0.01, rate=1.0, jitter=False)
|
|
143
|
+
|
|
144
|
+
call_count = 0
|
|
145
|
+
|
|
146
|
+
@retry
|
|
147
|
+
def always_fail():
|
|
148
|
+
nonlocal call_count
|
|
149
|
+
call_count += 1
|
|
150
|
+
raise ValueError("Always fails")
|
|
151
|
+
|
|
152
|
+
with pytest.raises(ValueError, match="Always fails"):
|
|
153
|
+
always_fail()
|
|
154
|
+
|
|
155
|
+
assert call_count == 3
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ==================== Exponential Backoff Tests ====================
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_retry_exponential_backoff():
|
|
162
|
+
"""Test retry with exponential backoff (rate>1.0)"""
|
|
163
|
+
retry = Retry(retry=4, wait=0.01, rate=2.0, jitter=False)
|
|
164
|
+
|
|
165
|
+
call_count = 0
|
|
166
|
+
|
|
167
|
+
@retry
|
|
168
|
+
def failing_func():
|
|
169
|
+
nonlocal call_count
|
|
170
|
+
call_count += 1
|
|
171
|
+
if call_count < 3:
|
|
172
|
+
raise ConnectionError("Connection failed")
|
|
173
|
+
return "connected"
|
|
174
|
+
|
|
175
|
+
result = failing_func()
|
|
176
|
+
|
|
177
|
+
assert result == "connected"
|
|
178
|
+
assert call_count == 3
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_retry_exponential_backoff_all_fail():
|
|
182
|
+
"""Test exponential backoff when all attempts fail"""
|
|
183
|
+
retry = Retry(retry=3, wait=0.01, rate=2.0, jitter=False)
|
|
184
|
+
|
|
185
|
+
call_count = 0
|
|
186
|
+
|
|
187
|
+
@retry
|
|
188
|
+
def always_fail():
|
|
189
|
+
nonlocal call_count
|
|
190
|
+
call_count += 1
|
|
191
|
+
raise TimeoutError("Timeout")
|
|
192
|
+
|
|
193
|
+
with pytest.raises(TimeoutError, match="Timeout"):
|
|
194
|
+
always_fail()
|
|
195
|
+
|
|
196
|
+
assert call_count == 3
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ==================== Exception Handling Tests ====================
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_retry_specific_exception():
|
|
203
|
+
"""Test retry only catches specified exceptions"""
|
|
204
|
+
retry = Retry(retry=3, wait=0.01, exceptions=[ValueError])
|
|
205
|
+
|
|
206
|
+
call_count = 0
|
|
207
|
+
|
|
208
|
+
@retry
|
|
209
|
+
def mixed_exceptions():
|
|
210
|
+
nonlocal call_count
|
|
211
|
+
call_count += 1
|
|
212
|
+
if call_count == 1:
|
|
213
|
+
raise ValueError("Retryable")
|
|
214
|
+
raise TypeError("Not retryable")
|
|
215
|
+
|
|
216
|
+
with pytest.raises(TypeError, match="Not retryable"):
|
|
217
|
+
mixed_exceptions()
|
|
218
|
+
|
|
219
|
+
# Should retry once for ValueError, then fail on TypeError
|
|
220
|
+
assert call_count == 2
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_retry_multiple_exceptions():
|
|
224
|
+
"""Test retry with multiple exception types"""
|
|
225
|
+
retry = Retry(retry=3, wait=0.01, exceptions=[ValueError, TypeError])
|
|
226
|
+
|
|
227
|
+
call_count = 0
|
|
228
|
+
|
|
229
|
+
@retry
|
|
230
|
+
def multiple_errors():
|
|
231
|
+
nonlocal call_count
|
|
232
|
+
call_count += 1
|
|
233
|
+
if call_count == 1:
|
|
234
|
+
raise ValueError("First error")
|
|
235
|
+
if call_count == 2:
|
|
236
|
+
raise TypeError("Second error")
|
|
237
|
+
return "success"
|
|
238
|
+
|
|
239
|
+
result = multiple_errors()
|
|
240
|
+
|
|
241
|
+
assert result == "success"
|
|
242
|
+
assert call_count == 3
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_retry_non_matching_exception():
|
|
246
|
+
"""Test retry doesn't catch non-matching exceptions"""
|
|
247
|
+
retry = Retry(retry=3, wait=0.01, exceptions=[ValueError])
|
|
248
|
+
|
|
249
|
+
call_count = 0
|
|
250
|
+
|
|
251
|
+
@retry
|
|
252
|
+
def wrong_exception():
|
|
253
|
+
nonlocal call_count
|
|
254
|
+
call_count += 1
|
|
255
|
+
raise KeyError("Not handled")
|
|
256
|
+
|
|
257
|
+
with pytest.raises(KeyError, match="Not handled"):
|
|
258
|
+
wrong_exception()
|
|
259
|
+
|
|
260
|
+
# Should fail immediately without retry
|
|
261
|
+
assert call_count == 1
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_retry_success_on_first_try():
|
|
265
|
+
"""Test function that succeeds on first try"""
|
|
266
|
+
retry = Retry(retry=3, wait=0.01)
|
|
267
|
+
|
|
268
|
+
call_count = 0
|
|
269
|
+
|
|
270
|
+
@retry
|
|
271
|
+
def success_func():
|
|
272
|
+
nonlocal call_count
|
|
273
|
+
call_count += 1
|
|
274
|
+
return "success"
|
|
275
|
+
|
|
276
|
+
result = success_func()
|
|
277
|
+
|
|
278
|
+
assert result == "success"
|
|
279
|
+
assert call_count == 1
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# ==================== Function Decoration Tests ====================
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_retry_preserves_function_name():
|
|
286
|
+
"""Test retry decorator preserves function name"""
|
|
287
|
+
retry = Retry(retry=3, wait=0.01)
|
|
288
|
+
|
|
289
|
+
@retry
|
|
290
|
+
def my_function():
|
|
291
|
+
return "result"
|
|
292
|
+
|
|
293
|
+
assert my_function.__name__ == "my_function"
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_retry_with_arguments():
|
|
297
|
+
"""Test retry works with functions that have arguments"""
|
|
298
|
+
retry = Retry(retry=3, wait=0.01)
|
|
299
|
+
|
|
300
|
+
call_count = 0
|
|
301
|
+
|
|
302
|
+
@retry
|
|
303
|
+
def func_with_args(a, b, c=10):
|
|
304
|
+
nonlocal call_count
|
|
305
|
+
call_count += 1
|
|
306
|
+
if call_count < 2:
|
|
307
|
+
raise ValueError("Fail")
|
|
308
|
+
return a + b + c
|
|
309
|
+
|
|
310
|
+
result = func_with_args(1, 2, c=3)
|
|
311
|
+
|
|
312
|
+
assert result == 6
|
|
313
|
+
assert call_count == 2
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_retry_with_kwargs():
|
|
317
|
+
"""Test retry works with keyword arguments"""
|
|
318
|
+
retry = Retry(retry=3, wait=0.01)
|
|
319
|
+
|
|
320
|
+
call_count = 0
|
|
321
|
+
|
|
322
|
+
@retry
|
|
323
|
+
def func_with_kwargs(**kwargs):
|
|
324
|
+
nonlocal call_count
|
|
325
|
+
call_count += 1
|
|
326
|
+
if call_count < 2:
|
|
327
|
+
raise ValueError("Fail")
|
|
328
|
+
return kwargs
|
|
329
|
+
|
|
330
|
+
result = func_with_kwargs(x=1, y=2, z=3)
|
|
331
|
+
|
|
332
|
+
assert result == {"x": 1, "y": 2, "z": 3}
|
|
333
|
+
assert call_count == 2
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def test_retry_with_mixed_args():
|
|
337
|
+
"""Test retry with positional and keyword arguments"""
|
|
338
|
+
retry = Retry(retry=3, wait=0.01)
|
|
339
|
+
|
|
340
|
+
call_count = 0
|
|
341
|
+
|
|
342
|
+
@retry
|
|
343
|
+
def mixed_args(a, b, *args, x=10, **kwargs):
|
|
344
|
+
nonlocal call_count
|
|
345
|
+
call_count += 1
|
|
346
|
+
if call_count < 2:
|
|
347
|
+
raise ValueError("Fail")
|
|
348
|
+
return a, b, args, x, kwargs
|
|
349
|
+
|
|
350
|
+
result = mixed_args(1, 2, 3, 4, x=5, y=6)
|
|
351
|
+
|
|
352
|
+
assert result == (1, 2, (3, 4), 5, {"y": 6})
|
|
353
|
+
assert call_count == 2
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_retry_decorator_as_variable():
|
|
357
|
+
"""Test using retry as a variable instead of decorator"""
|
|
358
|
+
retry = Retry(retry=3, wait=0.01)
|
|
359
|
+
|
|
360
|
+
call_count = 0
|
|
361
|
+
|
|
362
|
+
def original_func():
|
|
363
|
+
nonlocal call_count
|
|
364
|
+
call_count += 1
|
|
365
|
+
if call_count < 2:
|
|
366
|
+
raise ValueError("Fail")
|
|
367
|
+
return "success"
|
|
368
|
+
|
|
369
|
+
wrapped_func = retry(original_func)
|
|
370
|
+
result = wrapped_func()
|
|
371
|
+
|
|
372
|
+
assert result == "success"
|
|
373
|
+
assert call_count == 2
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# ==================== String Representation Tests ====================
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def test_retry_str():
|
|
380
|
+
"""Test __str__ returns retry count"""
|
|
381
|
+
retry = Retry(retry=5)
|
|
382
|
+
|
|
383
|
+
assert str(retry) == "5"
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def test_retry_str_zero():
|
|
387
|
+
"""Test __str__ with zero retry"""
|
|
388
|
+
retry = Retry(retry=0)
|
|
389
|
+
|
|
390
|
+
assert str(retry) == "0"
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def test_retry_repr():
|
|
394
|
+
"""Test __repr__ returns detailed representation"""
|
|
395
|
+
retry = Retry(retry=3, wait=1.5, rate=2.0)
|
|
396
|
+
|
|
397
|
+
repr_str = repr(retry)
|
|
398
|
+
|
|
399
|
+
assert "Retry" in repr_str
|
|
400
|
+
assert "retry=3" in repr_str
|
|
401
|
+
assert "wait=1.5" in repr_str
|
|
402
|
+
assert "rate=2.0" in repr_str
|
|
403
|
+
assert "exceptions=" in repr_str
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_retry_repr_custom_exceptions():
|
|
407
|
+
"""Test __repr__ with custom exceptions"""
|
|
408
|
+
retry = Retry(retry=3, exceptions=[ValueError, TypeError])
|
|
409
|
+
|
|
410
|
+
repr_str = repr(retry)
|
|
411
|
+
|
|
412
|
+
assert "ValueError" in repr_str
|
|
413
|
+
assert "TypeError" in repr_str
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# ==================== To Dict / Describe Tests ====================
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def test_retry_to_dict():
|
|
420
|
+
"""Test to_dict returns configuration dictionary"""
|
|
421
|
+
retry = Retry(retry=5, wait=2.0, rate=1.5)
|
|
422
|
+
|
|
423
|
+
result = retry.to_dict()
|
|
424
|
+
|
|
425
|
+
assert isinstance(result, dict)
|
|
426
|
+
assert result["retry"] == 5
|
|
427
|
+
assert result["wait"] == 2.0
|
|
428
|
+
assert result["rate"] == 1.5
|
|
429
|
+
assert "exceptions" in result
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def test_retry_to_dict_exception_names():
|
|
433
|
+
"""Test to_dict returns exception names as strings"""
|
|
434
|
+
retry = Retry(retry=3, exceptions=[ValueError, TypeError, ConnectionError])
|
|
435
|
+
|
|
436
|
+
result = retry.to_dict()
|
|
437
|
+
|
|
438
|
+
assert result["exceptions"] == ["ValueError", "TypeError", "ConnectionError"]
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_retry_describe():
|
|
442
|
+
"""Test describe returns same as to_dict"""
|
|
443
|
+
retry = Retry(retry=3, wait=1.0, rate=2.0)
|
|
444
|
+
|
|
445
|
+
describe_result = retry.describe()
|
|
446
|
+
to_dict_result = retry.to_dict()
|
|
447
|
+
|
|
448
|
+
assert describe_result == to_dict_result
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def test_retry_to_dict_default_exception():
|
|
452
|
+
"""Test to_dict with default Exception"""
|
|
453
|
+
retry = Retry()
|
|
454
|
+
|
|
455
|
+
result = retry.to_dict()
|
|
456
|
+
|
|
457
|
+
assert result["exceptions"] == ["Exception"]
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# ==================== Integration Tests ====================
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def test_retry_real_world_api_call():
|
|
464
|
+
"""Test retry with simulated API call scenario"""
|
|
465
|
+
retry = Retry(retry=4, wait=0.01, rate=2.0, exceptions=[ConnectionError, TimeoutError])
|
|
466
|
+
|
|
467
|
+
call_count = 0
|
|
468
|
+
errors = []
|
|
469
|
+
|
|
470
|
+
@retry
|
|
471
|
+
def api_call(endpoint):
|
|
472
|
+
nonlocal call_count
|
|
473
|
+
call_count += 1
|
|
474
|
+
|
|
475
|
+
if call_count == 1:
|
|
476
|
+
errors.append("connection_error")
|
|
477
|
+
raise ConnectionError("Network unreachable")
|
|
478
|
+
elif call_count == 2:
|
|
479
|
+
errors.append("timeout")
|
|
480
|
+
raise TimeoutError("Request timeout")
|
|
481
|
+
else:
|
|
482
|
+
return f"Data from {endpoint}"
|
|
483
|
+
|
|
484
|
+
result = api_call("/api/data")
|
|
485
|
+
|
|
486
|
+
assert result == "Data from /api/data"
|
|
487
|
+
assert call_count == 3
|
|
488
|
+
assert len(errors) == 2
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def test_retry_database_connection():
|
|
492
|
+
"""Test retry with simulated database connection scenario"""
|
|
493
|
+
retry = Retry(retry=3, wait=0.01, exceptions=[ConnectionError])
|
|
494
|
+
|
|
495
|
+
attempts = []
|
|
496
|
+
|
|
497
|
+
@retry
|
|
498
|
+
def connect_db(host, port):
|
|
499
|
+
attempts.append({"host": host, "port": port})
|
|
500
|
+
if len(attempts) < 3:
|
|
501
|
+
raise ConnectionError("Connection refused")
|
|
502
|
+
return f"Connected to {host}:{port}"
|
|
503
|
+
|
|
504
|
+
result = connect_db("localhost", 5432)
|
|
505
|
+
|
|
506
|
+
assert result == "Connected to localhost:5432"
|
|
507
|
+
assert len(attempts) == 3
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def test_retry_with_cleanup():
|
|
511
|
+
"""Test retry preserves function behavior with cleanup logic"""
|
|
512
|
+
retry = Retry(retry=3, wait=0.01)
|
|
513
|
+
|
|
514
|
+
resources = []
|
|
515
|
+
|
|
516
|
+
@retry
|
|
517
|
+
def process_with_cleanup():
|
|
518
|
+
resources.append("acquired")
|
|
519
|
+
try:
|
|
520
|
+
if len(resources) < 2:
|
|
521
|
+
raise ValueError("Processing failed")
|
|
522
|
+
return "success"
|
|
523
|
+
finally:
|
|
524
|
+
# Cleanup happens regardless
|
|
525
|
+
resources.append("released")
|
|
526
|
+
|
|
527
|
+
result = process_with_cleanup()
|
|
528
|
+
|
|
529
|
+
assert result == "success"
|
|
530
|
+
# Should have acquired and released for each attempt
|
|
531
|
+
assert len(resources) == 4 # 2 attempts * (acquire + release)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def test_retry_timing_constant():
|
|
535
|
+
"""Test retry timing with constant interval"""
|
|
536
|
+
retry = Retry(retry=3, wait=0.05, rate=1.0, jitter=False)
|
|
537
|
+
|
|
538
|
+
call_times = []
|
|
539
|
+
|
|
540
|
+
@retry
|
|
541
|
+
def timed_func():
|
|
542
|
+
call_times.append(time.time())
|
|
543
|
+
if len(call_times) < 3:
|
|
544
|
+
raise ValueError("Not yet")
|
|
545
|
+
return "done"
|
|
546
|
+
|
|
547
|
+
result = timed_func()
|
|
548
|
+
|
|
549
|
+
assert result == "done"
|
|
550
|
+
assert len(call_times) == 3
|
|
551
|
+
|
|
552
|
+
# Check intervals are approximately constant
|
|
553
|
+
interval1 = call_times[1] - call_times[0]
|
|
554
|
+
interval2 = call_times[2] - call_times[1]
|
|
555
|
+
|
|
556
|
+
assert 0.04 < interval1 < 0.15 # Allow some tolerance
|
|
557
|
+
assert 0.04 < interval2 < 0.15
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def test_retry_max_retries_exhausted():
|
|
561
|
+
"""Test behavior when max retries are exhausted"""
|
|
562
|
+
retry = Retry(retry=3, wait=0.01)
|
|
563
|
+
|
|
564
|
+
call_count = 0
|
|
565
|
+
|
|
566
|
+
@retry
|
|
567
|
+
def always_fails():
|
|
568
|
+
nonlocal call_count
|
|
569
|
+
call_count += 1
|
|
570
|
+
raise ValueError(f"Attempt {call_count}")
|
|
571
|
+
|
|
572
|
+
with pytest.raises(ValueError, match="Attempt 3"):
|
|
573
|
+
always_fails()
|
|
574
|
+
|
|
575
|
+
assert call_count == 3
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def test_retry_with_return_values():
|
|
579
|
+
"""Test retry with different return values"""
|
|
580
|
+
retry = Retry(retry=3, wait=0.01)
|
|
581
|
+
|
|
582
|
+
results = []
|
|
583
|
+
|
|
584
|
+
@retry
|
|
585
|
+
def generate_result():
|
|
586
|
+
results.append(len(results) + 1)
|
|
587
|
+
if len(results) < 2:
|
|
588
|
+
raise ValueError("Not ready")
|
|
589
|
+
return {"attempt": results[-1], "data": "success"}
|
|
590
|
+
|
|
591
|
+
result = generate_result()
|
|
592
|
+
|
|
593
|
+
assert result == {"attempt": 2, "data": "success"}
|
|
594
|
+
assert len(results) == 2
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def test_retry_class_method():
|
|
598
|
+
"""Test retry works with class methods"""
|
|
599
|
+
|
|
600
|
+
class Service:
|
|
601
|
+
def __init__(self):
|
|
602
|
+
self.attempts = 0
|
|
603
|
+
|
|
604
|
+
@Retry(retry=3, wait=0.01)
|
|
605
|
+
def fetch(self):
|
|
606
|
+
self.attempts += 1
|
|
607
|
+
if self.attempts < 2:
|
|
608
|
+
raise ConnectionError("Failed")
|
|
609
|
+
return "data"
|
|
610
|
+
|
|
611
|
+
service = Service()
|
|
612
|
+
result = service.fetch()
|
|
613
|
+
|
|
614
|
+
assert result == "data"
|
|
615
|
+
assert service.attempts == 2
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def test_retry_static_method():
|
|
619
|
+
"""Test retry works with static methods"""
|
|
620
|
+
|
|
621
|
+
call_count = 0
|
|
622
|
+
|
|
623
|
+
class Utils:
|
|
624
|
+
@staticmethod
|
|
625
|
+
@Retry(retry=3, wait=0.01)
|
|
626
|
+
def process():
|
|
627
|
+
nonlocal call_count
|
|
628
|
+
call_count += 1
|
|
629
|
+
if call_count < 2:
|
|
630
|
+
raise ValueError("Fail")
|
|
631
|
+
return "processed"
|
|
632
|
+
|
|
633
|
+
result = Utils.process()
|
|
634
|
+
|
|
635
|
+
assert result == "processed"
|
|
636
|
+
assert call_count == 2
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def test_retry_multiple_decorators():
|
|
640
|
+
"""Test retry can be combined with other decorators"""
|
|
641
|
+
retry = Retry(retry=3, wait=0.01)
|
|
642
|
+
|
|
643
|
+
call_log = []
|
|
644
|
+
attempt_count = 0
|
|
645
|
+
|
|
646
|
+
def log_calls(func):
|
|
647
|
+
def wrapper(*args, **kwargs):
|
|
648
|
+
call_log.append("outer_called")
|
|
649
|
+
return func(*args, **kwargs)
|
|
650
|
+
|
|
651
|
+
return wrapper
|
|
652
|
+
|
|
653
|
+
@log_calls
|
|
654
|
+
@retry
|
|
655
|
+
def decorated_func():
|
|
656
|
+
nonlocal attempt_count
|
|
657
|
+
attempt_count += 1
|
|
658
|
+
if attempt_count < 2:
|
|
659
|
+
raise ValueError("Fail")
|
|
660
|
+
return "success"
|
|
661
|
+
|
|
662
|
+
result = decorated_func()
|
|
663
|
+
|
|
664
|
+
assert result == "success"
|
|
665
|
+
# Log should be called once (outer decorator)
|
|
666
|
+
# but retry happens inside with 2 attempts
|
|
667
|
+
assert len(call_log) == 1
|
|
668
|
+
assert attempt_count == 2
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def test_retry_different_exception_types():
|
|
672
|
+
"""Test retry behavior with different exception types in sequence"""
|
|
673
|
+
retry = Retry(retry=4, wait=0.01, exceptions=[ValueError, TypeError, KeyError])
|
|
674
|
+
|
|
675
|
+
call_count = 0
|
|
676
|
+
|
|
677
|
+
@retry
|
|
678
|
+
def multi_exception_func():
|
|
679
|
+
nonlocal call_count
|
|
680
|
+
call_count += 1
|
|
681
|
+
|
|
682
|
+
if call_count == 1:
|
|
683
|
+
raise ValueError("Error 1")
|
|
684
|
+
elif call_count == 2:
|
|
685
|
+
raise TypeError("Error 2")
|
|
686
|
+
elif call_count == 3:
|
|
687
|
+
raise KeyError("Error 3")
|
|
688
|
+
return "finally succeeded"
|
|
689
|
+
|
|
690
|
+
result = multi_exception_func()
|
|
691
|
+
|
|
692
|
+
assert result == "finally succeeded"
|
|
693
|
+
assert call_count == 4
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def test_retry_configuration_immutability():
|
|
697
|
+
"""Test retry configuration doesn't change after decoration"""
|
|
698
|
+
retry = Retry(retry=3, wait=0.5, rate=2.0)
|
|
699
|
+
|
|
700
|
+
@retry
|
|
701
|
+
def some_func():
|
|
702
|
+
return "result"
|
|
703
|
+
|
|
704
|
+
# Configuration should remain unchanged
|
|
705
|
+
assert retry.retry == 3
|
|
706
|
+
assert retry.wait == 0.5
|
|
707
|
+
assert retry.rate == 2.0
|
|
708
|
+
|
|
709
|
+
# Calling function shouldn't affect configuration
|
|
710
|
+
some_func()
|
|
711
|
+
|
|
712
|
+
assert retry.retry == 3
|
|
713
|
+
assert retry.wait == 0.5
|
|
714
|
+
assert retry.rate == 2.0
|