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,801 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from xfintech.data.job.errors import (
|
|
4
|
+
JobAlreadyRegisteredError,
|
|
5
|
+
JobNameError,
|
|
6
|
+
JobNotFoundError,
|
|
7
|
+
)
|
|
8
|
+
from xfintech.data.job.house import House
|
|
9
|
+
|
|
10
|
+
# ==================== Test Helper Classes ====================
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _SampleJob:
|
|
14
|
+
"""Simple job class for testing"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, value: str = "default"):
|
|
17
|
+
self.value = value
|
|
18
|
+
|
|
19
|
+
def run(self):
|
|
20
|
+
return f"Running with {self.value}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _AnotherJob:
|
|
24
|
+
"""Another job class for testing"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, name: str, count: int = 0):
|
|
27
|
+
self.name = name
|
|
28
|
+
self.count = count
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ==================== House Initialization Tests ====================
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_jobhouse_init():
|
|
35
|
+
"""Test House initialization creates empty registries"""
|
|
36
|
+
house = House()
|
|
37
|
+
|
|
38
|
+
assert isinstance(house._jobs, dict)
|
|
39
|
+
assert isinstance(house._aliases, dict)
|
|
40
|
+
assert len(house._jobs) == 0
|
|
41
|
+
assert len(house._aliases) == 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_jobhouse_init_multiple_instances():
|
|
45
|
+
"""Test that multiple House instances are independent"""
|
|
46
|
+
house1 = House()
|
|
47
|
+
house2 = House()
|
|
48
|
+
|
|
49
|
+
@house1.register("job1")
|
|
50
|
+
class Job1:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
# house2 should not have job1
|
|
54
|
+
assert "job1" in house1._jobs
|
|
55
|
+
assert "job1" not in house2._jobs
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ==================== Name Normalization Tests ====================
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_normalize_name_lowercase():
|
|
62
|
+
"""Test _normalize_name converts to lowercase"""
|
|
63
|
+
normalized = House._normalize_name("MyJob")
|
|
64
|
+
|
|
65
|
+
assert normalized == "myjob"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_normalize_name_strips_whitespace():
|
|
69
|
+
"""Test _normalize_name strips leading/trailing whitespace"""
|
|
70
|
+
normalized = House._normalize_name(" my_job ")
|
|
71
|
+
|
|
72
|
+
assert normalized == "my_job"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_normalize_name_with_mixed_case():
|
|
76
|
+
"""Test _normalize_name with mixed case"""
|
|
77
|
+
normalized = House._normalize_name("Stock_Daily_Job")
|
|
78
|
+
|
|
79
|
+
assert normalized == "stock_daily_job"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_normalize_name_empty_string_raises_error():
|
|
83
|
+
"""Test _normalize_name raises JobNameError for empty string"""
|
|
84
|
+
with pytest.raises(JobNameError, match="job name cannot be empty"):
|
|
85
|
+
House._normalize_name("")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_normalize_name_whitespace_only_raises_error():
|
|
89
|
+
"""Test _normalize_name raises JobNameError for whitespace-only string"""
|
|
90
|
+
with pytest.raises(JobNameError, match="job name cannot be empty"):
|
|
91
|
+
House._normalize_name(" ")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_normalize_name_non_string_raises_error():
|
|
95
|
+
"""Test _normalize_name raises JobNameError for non-string input"""
|
|
96
|
+
with pytest.raises(JobNameError, match="job name must be str"):
|
|
97
|
+
House._normalize_name(123)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_normalize_name_none_raises_error():
|
|
101
|
+
"""Test _normalize_name raises JobNameError for None"""
|
|
102
|
+
with pytest.raises(JobNameError, match="job name must be str"):
|
|
103
|
+
House._normalize_name(None)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ==================== register() Decorator Tests ====================
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_register_simple_job():
|
|
110
|
+
"""Test registering a simple job class"""
|
|
111
|
+
house = House()
|
|
112
|
+
|
|
113
|
+
@house.register("my_job")
|
|
114
|
+
class MyJob:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
assert "my_job" in house._jobs
|
|
118
|
+
assert house._jobs["my_job"] is MyJob
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_register_adds_job_name_attribute():
|
|
122
|
+
"""Test register adds __job_name__ attribute to class"""
|
|
123
|
+
house = House()
|
|
124
|
+
|
|
125
|
+
@house.register("test_job")
|
|
126
|
+
class TestJob:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
assert hasattr(TestJob, "__job_name__")
|
|
130
|
+
assert TestJob.__job_name__ == "test_job"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_register_with_alias():
|
|
134
|
+
"""Test registering job with alias"""
|
|
135
|
+
house = House()
|
|
136
|
+
|
|
137
|
+
@house.register("stock_daily", alias="daily")
|
|
138
|
+
class StockDailyJob:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
assert "stock_daily" in house._jobs
|
|
142
|
+
assert "daily" in house._aliases
|
|
143
|
+
assert house._aliases["daily"] == "stock_daily"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_register_multiple_jobs():
|
|
147
|
+
"""Test registering multiple jobs"""
|
|
148
|
+
house = House()
|
|
149
|
+
|
|
150
|
+
@house.register("job1")
|
|
151
|
+
class Job1:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
@house.register("job2")
|
|
155
|
+
class Job2:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
assert len(house._jobs) == 2
|
|
159
|
+
assert "job1" in house._jobs
|
|
160
|
+
assert "job2" in house._jobs
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_register_normalizes_name():
|
|
164
|
+
"""Test register normalizes job name"""
|
|
165
|
+
house = House()
|
|
166
|
+
|
|
167
|
+
@house.register("My_Job")
|
|
168
|
+
class MyJob:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
assert "my_job" in house._jobs
|
|
172
|
+
assert "My_Job" not in house._jobs
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_register_duplicate_alias_raises_error():
|
|
176
|
+
"""Test registering duplicate alias raises JobAlreadyRegisteredError"""
|
|
177
|
+
house = House()
|
|
178
|
+
|
|
179
|
+
@house.register("job1", alias="shared")
|
|
180
|
+
class Job1:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
with pytest.raises(JobAlreadyRegisteredError, match="Alias already used: shared"):
|
|
184
|
+
|
|
185
|
+
@house.register("job2", alias="shared")
|
|
186
|
+
class Job2:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_register_with_replace_true():
|
|
191
|
+
"""Test replace=True allows overwriting existing job"""
|
|
192
|
+
house = House()
|
|
193
|
+
|
|
194
|
+
@house.register("my_job")
|
|
195
|
+
class OriginalJob:
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
@house.register("my_job", replace=True)
|
|
199
|
+
class ReplacedJob:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
assert house._jobs["my_job"] is ReplacedJob
|
|
203
|
+
assert house._jobs["my_job"] is not OriginalJob
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_register_with_replace_true_for_alias():
|
|
207
|
+
"""Test replace=True allows overwriting existing alias"""
|
|
208
|
+
house = House()
|
|
209
|
+
|
|
210
|
+
@house.register("job1", alias="shared")
|
|
211
|
+
class Job1:
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
@house.register("job2", alias="shared", replace=True)
|
|
215
|
+
class Job2:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
assert house._aliases["shared"] == "job2"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_register_same_alias_same_job_no_error():
|
|
222
|
+
"""Test registering same alias for same job doesn't raise error"""
|
|
223
|
+
house = House()
|
|
224
|
+
|
|
225
|
+
@house.register("my_job", alias="alias1")
|
|
226
|
+
class MyJob:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
# Re-registering with replace, same alias should work
|
|
230
|
+
@house.register("my_job", alias="alias1", replace=True)
|
|
231
|
+
class MyJob2:
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
assert house._aliases["alias1"] == "my_job"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_register_empty_name_raises_error():
|
|
238
|
+
"""Test register with empty name raises JobNameError"""
|
|
239
|
+
house = House()
|
|
240
|
+
|
|
241
|
+
with pytest.raises(JobNameError, match="job name cannot be empty"):
|
|
242
|
+
|
|
243
|
+
@house.register("")
|
|
244
|
+
class MyJob:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def test_register_with_uppercase_alias():
|
|
249
|
+
"""Test register normalizes alias name"""
|
|
250
|
+
house = House()
|
|
251
|
+
|
|
252
|
+
@house.register("job", alias="MY_ALIAS")
|
|
253
|
+
class MyJob:
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
assert "my_alias" in house._aliases
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ==================== lookup() Method Tests ====================
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_lookup_by_name():
|
|
263
|
+
"""Test looking up job by registered name"""
|
|
264
|
+
house = House()
|
|
265
|
+
|
|
266
|
+
@house.register("my_job")
|
|
267
|
+
class MyJob:
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
result = house.lookup("my_job")
|
|
271
|
+
|
|
272
|
+
assert result is MyJob
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_lookup_by_alias():
|
|
276
|
+
"""Test looking up job by alias"""
|
|
277
|
+
house = House()
|
|
278
|
+
|
|
279
|
+
@house.register("stock_daily", alias="daily")
|
|
280
|
+
class StockDailyJob:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
result = house.lookup("daily")
|
|
284
|
+
|
|
285
|
+
assert result is StockDailyJob
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_lookup_case_insensitive():
|
|
289
|
+
"""Test lookup is case insensitive"""
|
|
290
|
+
house = House()
|
|
291
|
+
|
|
292
|
+
@house.register("my_job")
|
|
293
|
+
class MyJob:
|
|
294
|
+
pass
|
|
295
|
+
|
|
296
|
+
result1 = house.lookup("MY_JOB")
|
|
297
|
+
result2 = house.lookup("My_Job")
|
|
298
|
+
result3 = house.lookup("my_job")
|
|
299
|
+
|
|
300
|
+
assert result1 is MyJob
|
|
301
|
+
assert result2 is MyJob
|
|
302
|
+
assert result3 is MyJob
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def test_lookup_strips_whitespace():
|
|
306
|
+
"""Test lookup strips whitespace from name"""
|
|
307
|
+
house = House()
|
|
308
|
+
|
|
309
|
+
@house.register("my_job")
|
|
310
|
+
class MyJob:
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
result = house.lookup(" my_job ")
|
|
314
|
+
|
|
315
|
+
assert result is MyJob
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test_lookup_nonexistent_raises_error():
|
|
319
|
+
"""Test lookup raises JobNotFoundError for nonexistent job"""
|
|
320
|
+
house = House()
|
|
321
|
+
|
|
322
|
+
with pytest.raises(JobNotFoundError, match="job not found: nonexistent"):
|
|
323
|
+
house.lookup("nonexistent")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def test_lookup_after_multiple_registrations():
|
|
327
|
+
"""Test lookup works correctly with multiple jobs"""
|
|
328
|
+
house = House()
|
|
329
|
+
|
|
330
|
+
@house.register("job1")
|
|
331
|
+
class Job1:
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
@house.register("job2")
|
|
335
|
+
class Job2:
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
@house.register("job3")
|
|
339
|
+
class Job3:
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
assert house.lookup("job1") is Job1
|
|
343
|
+
assert house.lookup("job2") is Job2
|
|
344
|
+
assert house.lookup("job3") is Job3
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_lookup_empty_string_raises_error():
|
|
348
|
+
"""Test lookup with empty string raises JobNameError"""
|
|
349
|
+
house = House()
|
|
350
|
+
|
|
351
|
+
with pytest.raises(JobNameError, match="job name cannot be empty"):
|
|
352
|
+
house.lookup("")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# ==================== create() Method Tests ====================
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_create_simple_job():
|
|
359
|
+
"""Test creating job instance without arguments"""
|
|
360
|
+
house = House()
|
|
361
|
+
|
|
362
|
+
@house.register("sample_job")
|
|
363
|
+
class SampleJob:
|
|
364
|
+
def __init__(self):
|
|
365
|
+
self.value = "test"
|
|
366
|
+
|
|
367
|
+
instance = house.create("sample_job")
|
|
368
|
+
|
|
369
|
+
assert isinstance(instance, SampleJob)
|
|
370
|
+
assert instance.value == "test"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def test_create_job_with_args():
|
|
374
|
+
"""Test creating job instance with positional arguments"""
|
|
375
|
+
house = House()
|
|
376
|
+
|
|
377
|
+
@house.register("job_with_args")
|
|
378
|
+
class JobWithArgs:
|
|
379
|
+
def __init__(self, arg1, arg2):
|
|
380
|
+
self.arg1 = arg1
|
|
381
|
+
self.arg2 = arg2
|
|
382
|
+
|
|
383
|
+
instance = house.create("job_with_args", "value1", "value2")
|
|
384
|
+
|
|
385
|
+
assert instance.arg1 == "value1"
|
|
386
|
+
assert instance.arg2 == "value2"
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def test_create_job_with_kwargs():
|
|
390
|
+
"""Test creating job instance with keyword arguments"""
|
|
391
|
+
house = House()
|
|
392
|
+
|
|
393
|
+
@house.register("job_with_kwargs")
|
|
394
|
+
class JobWithKwargs:
|
|
395
|
+
def __init__(self, title="default", count=0):
|
|
396
|
+
self.title = title
|
|
397
|
+
self.count = count
|
|
398
|
+
|
|
399
|
+
instance = house.create("job_with_kwargs", title="test", count=5)
|
|
400
|
+
|
|
401
|
+
assert instance.title == "test"
|
|
402
|
+
assert instance.count == 5
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_create_job_with_mixed_args():
|
|
406
|
+
"""Test creating job instance with both args and kwargs"""
|
|
407
|
+
house = House()
|
|
408
|
+
|
|
409
|
+
@house.register("mixed_job")
|
|
410
|
+
class MixedJob:
|
|
411
|
+
def __init__(self, pos_arg, keyword_arg="default"):
|
|
412
|
+
self.pos_arg = pos_arg
|
|
413
|
+
self.keyword_arg = keyword_arg
|
|
414
|
+
|
|
415
|
+
instance = house.create("mixed_job", "positional", keyword_arg="keyword")
|
|
416
|
+
|
|
417
|
+
assert instance.pos_arg == "positional"
|
|
418
|
+
assert instance.keyword_arg == "keyword"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def test_create_by_alias():
|
|
422
|
+
"""Test creating job instance using alias"""
|
|
423
|
+
house = House()
|
|
424
|
+
|
|
425
|
+
@house.register("long_name", alias="short")
|
|
426
|
+
class MyJob:
|
|
427
|
+
def __init__(self, data):
|
|
428
|
+
self.data = data
|
|
429
|
+
|
|
430
|
+
instance = house.create("short", data="test")
|
|
431
|
+
|
|
432
|
+
assert isinstance(instance, MyJob)
|
|
433
|
+
assert instance.data == "test"
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def test_create_case_insensitive():
|
|
437
|
+
"""Test create is case insensitive"""
|
|
438
|
+
house = House()
|
|
439
|
+
|
|
440
|
+
@house.register("my_job")
|
|
441
|
+
class MyJob:
|
|
442
|
+
def __init__(self):
|
|
443
|
+
self.created = True
|
|
444
|
+
|
|
445
|
+
instance = house.create("MY_JOB")
|
|
446
|
+
|
|
447
|
+
assert isinstance(instance, MyJob)
|
|
448
|
+
assert instance.created is True
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def test_create_nonexistent_raises_error():
|
|
452
|
+
"""Test create raises JobNotFoundError for nonexistent job"""
|
|
453
|
+
house = House()
|
|
454
|
+
|
|
455
|
+
with pytest.raises(JobNotFoundError, match="job not found: nonexistent"):
|
|
456
|
+
house.create("nonexistent")
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def test_create_multiple_instances():
|
|
460
|
+
"""Test creating multiple instances of same job"""
|
|
461
|
+
house = House()
|
|
462
|
+
|
|
463
|
+
@house.register("counter_job")
|
|
464
|
+
class CounterJob:
|
|
465
|
+
def __init__(self, start=0):
|
|
466
|
+
self.count = start
|
|
467
|
+
|
|
468
|
+
instance1 = house.create("counter_job", start=10)
|
|
469
|
+
instance2 = house.create("counter_job", start=20)
|
|
470
|
+
|
|
471
|
+
assert instance1.count == 10
|
|
472
|
+
assert instance2.count == 20
|
|
473
|
+
assert instance1 is not instance2
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ==================== list() Method Tests ====================
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def test_list_empty_house():
|
|
480
|
+
"""Test list returns empty list for new House"""
|
|
481
|
+
house = House()
|
|
482
|
+
|
|
483
|
+
result = house.list()
|
|
484
|
+
|
|
485
|
+
assert result == []
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def test_list_single_job():
|
|
489
|
+
"""Test list returns single registered job"""
|
|
490
|
+
house = House()
|
|
491
|
+
|
|
492
|
+
@house.register("my_job")
|
|
493
|
+
class MyJob:
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
result = house.list()
|
|
497
|
+
|
|
498
|
+
assert result == ["my_job"]
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_list_multiple_jobs():
|
|
502
|
+
"""Test list returns all registered jobs"""
|
|
503
|
+
house = House()
|
|
504
|
+
|
|
505
|
+
@house.register("job_a")
|
|
506
|
+
class JobA:
|
|
507
|
+
pass
|
|
508
|
+
|
|
509
|
+
@house.register("job_c")
|
|
510
|
+
class JobC:
|
|
511
|
+
pass
|
|
512
|
+
|
|
513
|
+
@house.register("job_b")
|
|
514
|
+
class JobB:
|
|
515
|
+
pass
|
|
516
|
+
|
|
517
|
+
result = house.list()
|
|
518
|
+
|
|
519
|
+
assert len(result) == 3
|
|
520
|
+
assert "job_a" in result
|
|
521
|
+
assert "job_b" in result
|
|
522
|
+
assert "job_c" in result
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def test_list_returns_sorted():
|
|
526
|
+
"""Test list returns jobs in sorted order"""
|
|
527
|
+
house = House()
|
|
528
|
+
|
|
529
|
+
@house.register("zebra")
|
|
530
|
+
class Zebra:
|
|
531
|
+
pass
|
|
532
|
+
|
|
533
|
+
@house.register("alpha")
|
|
534
|
+
class Alpha:
|
|
535
|
+
pass
|
|
536
|
+
|
|
537
|
+
@house.register("beta")
|
|
538
|
+
class Beta:
|
|
539
|
+
pass
|
|
540
|
+
|
|
541
|
+
result = house.list()
|
|
542
|
+
|
|
543
|
+
assert result == ["alpha", "beta", "zebra"]
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def test_list_does_not_include_aliases():
|
|
547
|
+
"""Test list does not include aliases, only job names"""
|
|
548
|
+
house = House()
|
|
549
|
+
|
|
550
|
+
@house.register("job1", alias="alias1")
|
|
551
|
+
class Job1:
|
|
552
|
+
pass
|
|
553
|
+
|
|
554
|
+
@house.register("job2", alias="alias2")
|
|
555
|
+
class Job2:
|
|
556
|
+
pass
|
|
557
|
+
|
|
558
|
+
result = house.list()
|
|
559
|
+
|
|
560
|
+
assert result == ["alias1", "alias2", "job1", "job2"]
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def test_list_after_replace():
|
|
564
|
+
"""Test list reflects replaced jobs correctly"""
|
|
565
|
+
house = House()
|
|
566
|
+
|
|
567
|
+
@house.register("my_job")
|
|
568
|
+
class OriginalJob:
|
|
569
|
+
pass
|
|
570
|
+
|
|
571
|
+
@house.register("my_job", replace=True)
|
|
572
|
+
class ReplacedJob:
|
|
573
|
+
pass
|
|
574
|
+
|
|
575
|
+
result = house.list()
|
|
576
|
+
|
|
577
|
+
assert result == ["my_job"]
|
|
578
|
+
assert len(result) == 1
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
# ==================== Integration Tests ====================
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def test_full_workflow():
|
|
585
|
+
"""Test complete workflow: register -> lookup -> create"""
|
|
586
|
+
house = House()
|
|
587
|
+
|
|
588
|
+
@house.register("data_processor", alias="processor")
|
|
589
|
+
class DataProcessor:
|
|
590
|
+
def __init__(self, data_source: str):
|
|
591
|
+
self.data_source = data_source
|
|
592
|
+
self.processed = False
|
|
593
|
+
|
|
594
|
+
def process(self):
|
|
595
|
+
self.processed = True
|
|
596
|
+
return f"Processed data from {self.data_source}"
|
|
597
|
+
|
|
598
|
+
# Lookup by name
|
|
599
|
+
ProcessorClass = house.lookup("data_processor")
|
|
600
|
+
assert ProcessorClass is DataProcessor
|
|
601
|
+
|
|
602
|
+
# Lookup by alias
|
|
603
|
+
ProcessorClass = house.lookup("processor")
|
|
604
|
+
assert ProcessorClass is DataProcessor
|
|
605
|
+
|
|
606
|
+
# Create instance
|
|
607
|
+
processor = house.create("data_processor", data_source="API")
|
|
608
|
+
assert isinstance(processor, DataProcessor)
|
|
609
|
+
assert processor.data_source == "API"
|
|
610
|
+
assert processor.processed is False
|
|
611
|
+
|
|
612
|
+
# Use the instance
|
|
613
|
+
result = processor.process()
|
|
614
|
+
assert processor.processed is True
|
|
615
|
+
assert result == "Processed data from API"
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def test_multiple_aliases_different_jobs():
|
|
619
|
+
"""Test multiple jobs with their own aliases"""
|
|
620
|
+
house = House()
|
|
621
|
+
|
|
622
|
+
@house.register("stock_daily", alias="daily")
|
|
623
|
+
class StockDailyJob:
|
|
624
|
+
pass
|
|
625
|
+
|
|
626
|
+
@house.register("stock_weekly", alias="weekly")
|
|
627
|
+
class StockWeeklyJob:
|
|
628
|
+
pass
|
|
629
|
+
|
|
630
|
+
assert house.lookup("daily") is StockDailyJob
|
|
631
|
+
assert house.lookup("weekly") is StockWeeklyJob
|
|
632
|
+
assert house.lookup("stock_daily") is StockDailyJob
|
|
633
|
+
assert house.lookup("stock_weekly") is StockWeeklyJob
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def test_case_insensitive_throughout():
|
|
637
|
+
"""Test case insensitivity across all operations"""
|
|
638
|
+
house = House()
|
|
639
|
+
|
|
640
|
+
@house.register("MyJob", alias="MyAlias")
|
|
641
|
+
class MyJob:
|
|
642
|
+
def __init__(self, value):
|
|
643
|
+
self.value = value
|
|
644
|
+
|
|
645
|
+
# All should work
|
|
646
|
+
assert house.lookup("MYJOB") is MyJob
|
|
647
|
+
assert house.lookup("myjob") is MyJob
|
|
648
|
+
assert house.lookup("MyJob") is MyJob
|
|
649
|
+
assert house.lookup("MYALIAS") is MyJob
|
|
650
|
+
assert house.lookup("myalias") is MyJob
|
|
651
|
+
|
|
652
|
+
instance = house.create("MYJOB", value="test")
|
|
653
|
+
assert instance.value == "test"
|
|
654
|
+
|
|
655
|
+
jobs = house.list()
|
|
656
|
+
assert "myjob" in jobs
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def test_job_with_complex_initialization():
|
|
660
|
+
"""Test job with complex initialization logic"""
|
|
661
|
+
house = House()
|
|
662
|
+
|
|
663
|
+
@house.register("complex_job")
|
|
664
|
+
class ComplexJob:
|
|
665
|
+
def __init__(self, config: dict, timeout: int = 30, **options):
|
|
666
|
+
self.config = config
|
|
667
|
+
self.timeout = timeout
|
|
668
|
+
self.options = options
|
|
669
|
+
|
|
670
|
+
instance = house.create(
|
|
671
|
+
"complex_job",
|
|
672
|
+
config={"host": "localhost", "port": 8080},
|
|
673
|
+
timeout=60,
|
|
674
|
+
retry=3,
|
|
675
|
+
verbose=True,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
assert instance.config == {"host": "localhost", "port": 8080}
|
|
679
|
+
assert instance.timeout == 60
|
|
680
|
+
assert instance.options == {"retry": 3, "verbose": True}
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def test_replacing_job_updates_lookup():
|
|
684
|
+
"""Test that replacing a job updates lookup results"""
|
|
685
|
+
house = House()
|
|
686
|
+
|
|
687
|
+
@house.register("my_job")
|
|
688
|
+
class OriginalJob:
|
|
689
|
+
version = 1
|
|
690
|
+
|
|
691
|
+
assert house.lookup("my_job").version == 1
|
|
692
|
+
|
|
693
|
+
@house.register("my_job", replace=True)
|
|
694
|
+
class NewJob:
|
|
695
|
+
version = 2
|
|
696
|
+
|
|
697
|
+
assert house.lookup("my_job").version == 2
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
# ==================== Error Handling Tests ====================
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def test_job_not_found_error_inheritance():
|
|
704
|
+
"""Test JobNotFoundError inherits from KeyError"""
|
|
705
|
+
assert issubclass(JobNotFoundError, KeyError)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def test_job_already_registered_error_inheritance():
|
|
709
|
+
"""Test JobAlreadyRegisteredError inherits from KeyError"""
|
|
710
|
+
assert issubclass(JobAlreadyRegisteredError, KeyError)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def test_job_name_error_inheritance():
|
|
714
|
+
"""Test JobNameError inherits from ValueError"""
|
|
715
|
+
assert issubclass(JobNameError, ValueError)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def test_error_messages_are_descriptive():
|
|
719
|
+
"""Test that error messages contain useful information"""
|
|
720
|
+
house = House()
|
|
721
|
+
|
|
722
|
+
# JobNotFoundError
|
|
723
|
+
try:
|
|
724
|
+
house.lookup("missing_job")
|
|
725
|
+
except JobNotFoundError as e:
|
|
726
|
+
assert "missing_job" in str(e)
|
|
727
|
+
|
|
728
|
+
# JobAlreadyRegisteredError
|
|
729
|
+
@house.register("duplicate")
|
|
730
|
+
class Job1:
|
|
731
|
+
pass
|
|
732
|
+
|
|
733
|
+
try:
|
|
734
|
+
|
|
735
|
+
@house.register("duplicate")
|
|
736
|
+
class Job2:
|
|
737
|
+
pass
|
|
738
|
+
except JobAlreadyRegisteredError as e:
|
|
739
|
+
assert "duplicate" in str(e)
|
|
740
|
+
|
|
741
|
+
# JobNameError
|
|
742
|
+
try:
|
|
743
|
+
house.lookup("")
|
|
744
|
+
except JobNameError as e:
|
|
745
|
+
assert "empty" in str(e).lower()
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
# ==================== Edge Cases ====================
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def test_job_with_no_init():
|
|
752
|
+
"""Test registering job class without __init__"""
|
|
753
|
+
house = House()
|
|
754
|
+
|
|
755
|
+
@house.register("simple")
|
|
756
|
+
class SimpleJob:
|
|
757
|
+
pass
|
|
758
|
+
|
|
759
|
+
instance = house.create("simple")
|
|
760
|
+
assert isinstance(instance, SimpleJob)
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def test_special_characters_in_name():
|
|
764
|
+
"""Test job names with underscores and numbers"""
|
|
765
|
+
house = House()
|
|
766
|
+
|
|
767
|
+
@house.register("job_123")
|
|
768
|
+
class Job123:
|
|
769
|
+
pass
|
|
770
|
+
|
|
771
|
+
@house.register("another_job_456")
|
|
772
|
+
class AnotherJob:
|
|
773
|
+
pass
|
|
774
|
+
|
|
775
|
+
assert house.lookup("job_123") is Job123
|
|
776
|
+
assert house.lookup("another_job_456") is AnotherJob
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def test_very_long_job_name():
|
|
780
|
+
"""Test handling very long job names"""
|
|
781
|
+
house = House()
|
|
782
|
+
long_name = "very_long_job_name_" * 10
|
|
783
|
+
|
|
784
|
+
@house.register(long_name)
|
|
785
|
+
class LongNameJob:
|
|
786
|
+
pass
|
|
787
|
+
|
|
788
|
+
result = house.lookup(long_name)
|
|
789
|
+
assert result is LongNameJob
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def test_unicode_in_job_name():
|
|
793
|
+
"""Test job names with unicode characters (normalized to lowercase)"""
|
|
794
|
+
house = House()
|
|
795
|
+
|
|
796
|
+
@house.register("job_测试")
|
|
797
|
+
class UnicodeJob:
|
|
798
|
+
pass
|
|
799
|
+
|
|
800
|
+
result = house.lookup("job_测试")
|
|
801
|
+
assert result is UnicodeJob
|