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.
Files changed (282) hide show
  1. lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
  2. lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
  3. lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
  4. lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
  5. lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
  6. xfintech/__init__.py +0 -0
  7. xfintech/connect/__init__.py +18 -0
  8. xfintech/connect/artifact/__init__.py +5 -0
  9. xfintech/connect/artifact/artifact.py +168 -0
  10. xfintech/connect/artifact/tests/__init__.py +3 -0
  11. xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
  12. xfintech/connect/common/__init__.py +12 -0
  13. xfintech/connect/common/connect.py +49 -0
  14. xfintech/connect/common/connectref.py +119 -0
  15. xfintech/connect/common/error.py +62 -0
  16. xfintech/connect/common/tests/__init__.py +1 -0
  17. xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
  18. xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
  19. xfintech/connect/common/tests/test_class_errors_all.py +524 -0
  20. xfintech/connect/instance/__init__.py +7 -0
  21. xfintech/connect/instance/macos.py +121 -0
  22. xfintech/connect/instance/s3.py +176 -0
  23. xfintech/connect/instance/tests/__init__.py +1 -0
  24. xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
  25. xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
  26. xfintech/data/__init__.py +20 -0
  27. xfintech/data/common/__init__.py +15 -0
  28. xfintech/data/common/cache.py +186 -0
  29. xfintech/data/common/coolant.py +171 -0
  30. xfintech/data/common/metric.py +138 -0
  31. xfintech/data/common/paginate.py +132 -0
  32. xfintech/data/common/params.py +162 -0
  33. xfintech/data/common/retry.py +201 -0
  34. xfintech/data/common/tests/__init__.py +1 -0
  35. xfintech/data/common/tests/test_class_cache_all.py +681 -0
  36. xfintech/data/common/tests/test_class_coolant_all.py +534 -0
  37. xfintech/data/common/tests/test_class_metric_all.py +705 -0
  38. xfintech/data/common/tests/test_class_paginate_all.py +508 -0
  39. xfintech/data/common/tests/test_class_params_all.py +891 -0
  40. xfintech/data/common/tests/test_class_retry_all.py +714 -0
  41. xfintech/data/job/__init__.py +17 -0
  42. xfintech/data/job/errors.py +112 -0
  43. xfintech/data/job/house.py +156 -0
  44. xfintech/data/job/job.py +247 -0
  45. xfintech/data/job/joblike.py +47 -0
  46. xfintech/data/job/tests/__init__.py +1 -0
  47. xfintech/data/job/tests/test_class_errors_all.py +275 -0
  48. xfintech/data/job/tests/test_class_house_all.py +801 -0
  49. xfintech/data/job/tests/test_class_job_all.py +684 -0
  50. xfintech/data/job/tests/test_class_joblike_all.py +482 -0
  51. xfintech/data/relay/__init__.py +7 -0
  52. xfintech/data/relay/client.py +114 -0
  53. xfintech/data/relay/clientlike.py +45 -0
  54. xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
  55. xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
  56. xfintech/data/source/__init__.py +7 -0
  57. xfintech/data/source/baostock/__init__.py +21 -0
  58. xfintech/data/source/baostock/job/__init__.py +5 -0
  59. xfintech/data/source/baostock/job/job.py +217 -0
  60. xfintech/data/source/baostock/job/tests/__init__.py +0 -0
  61. xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
  62. xfintech/data/source/baostock/session/__init__.py +8 -0
  63. xfintech/data/source/baostock/session/relay.py +223 -0
  64. xfintech/data/source/baostock/session/session.py +241 -0
  65. xfintech/data/source/baostock/session/tests/__init__.py +0 -0
  66. xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
  67. xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
  68. xfintech/data/source/baostock/stock/__init__.py +0 -0
  69. xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
  70. xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
  71. xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
  72. xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
  73. xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
  74. xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
  75. xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
  76. xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
  77. xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
  78. xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
  79. xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
  80. xfintech/data/source/baostock/stock/stock/constant.py +55 -0
  81. xfintech/data/source/baostock/stock/stock/stock.py +149 -0
  82. xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
  83. xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
  84. xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
  85. xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
  86. xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
  87. xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
  88. xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
  89. xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
  90. xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
  91. xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
  92. xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
  93. xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
  94. xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
  95. xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
  96. xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
  97. xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
  98. xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
  99. xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
  100. xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
  101. xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
  102. xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
  103. xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
  104. xfintech/data/source/tushare/__init__.py +61 -0
  105. xfintech/data/source/tushare/job/__init__.py +5 -0
  106. xfintech/data/source/tushare/job/job.py +257 -0
  107. xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
  108. xfintech/data/source/tushare/session/__init__.py +5 -0
  109. xfintech/data/source/tushare/session/relay.py +231 -0
  110. xfintech/data/source/tushare/session/session.py +239 -0
  111. xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
  112. xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
  113. xfintech/data/source/tushare/stock/__init__.py +55 -0
  114. xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
  115. xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
  116. xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
  117. xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
  118. xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
  119. xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
  120. xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
  121. xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
  122. xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
  123. xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
  124. xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
  125. xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
  126. xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
  127. xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
  128. xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
  129. xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
  130. xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
  131. xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
  132. xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
  133. xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
  134. xfintech/data/source/tushare/stock/company/__init__.py +19 -0
  135. xfintech/data/source/tushare/stock/company/company.py +188 -0
  136. xfintech/data/source/tushare/stock/company/constant.py +92 -0
  137. xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
  138. xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
  139. xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
  140. xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
  141. xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
  142. xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
  143. xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
  144. xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
  145. xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
  146. xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
  147. xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
  148. xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
  149. xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
  150. xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
  151. xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
  152. xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
  153. xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
  154. xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
  155. xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
  156. xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
  157. xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
  158. xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
  159. xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
  160. xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
  161. xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
  162. xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
  163. xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
  164. xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
  165. xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
  166. xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
  167. xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
  168. xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
  169. xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
  170. xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
  171. xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
  172. xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
  173. xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
  174. xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
  175. xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
  176. xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
  177. xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
  178. xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
  179. xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
  180. xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
  181. xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
  182. xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
  183. xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
  184. xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
  185. xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
  186. xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
  187. xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
  188. xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
  189. xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
  190. xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
  191. xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
  192. xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
  193. xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
  194. xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
  195. xfintech/data/source/tushare/stock/stock/constant.py +105 -0
  196. xfintech/data/source/tushare/stock/stock/stock.py +193 -0
  197. xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
  198. xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
  199. xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
  200. xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
  201. xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
  202. xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
  203. xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
  204. xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
  205. xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
  206. xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
  207. xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
  208. xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
  209. xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
  210. xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
  211. xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
  212. xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
  213. xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
  214. xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
  215. xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
  216. xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
  217. xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
  218. xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
  219. xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
  220. xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
  221. xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
  222. xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
  223. xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
  224. xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
  225. xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
  226. xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
  227. xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
  228. xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
  229. xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
  230. xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
  231. xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
  232. xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
  233. xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
  234. xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
  235. xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
  236. xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
  237. xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
  238. xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
  239. xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
  240. xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
  241. xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
  242. xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
  243. xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
  244. xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
  245. xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
  246. xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
  247. xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
  248. xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
  249. xfintech/fabric/__init__.py +18 -0
  250. xfintech/fabric/column/__init__.py +7 -0
  251. xfintech/fabric/column/info.py +202 -0
  252. xfintech/fabric/column/kind.py +102 -0
  253. xfintech/fabric/column/tests/__init__.py +0 -0
  254. xfintech/fabric/column/tests/test_class_info_all.py +207 -0
  255. xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
  256. xfintech/fabric/table/__init__.py +5 -0
  257. xfintech/fabric/table/info.py +263 -0
  258. xfintech/fabric/table/tests/__init__.py +0 -0
  259. xfintech/fabric/table/tests/test_class_info_all.py +547 -0
  260. xfintech/serde/__init__.py +35 -0
  261. xfintech/serde/common/__init__.py +9 -0
  262. xfintech/serde/common/dataformat.py +78 -0
  263. xfintech/serde/common/deserialiserlike.py +38 -0
  264. xfintech/serde/common/error.py +182 -0
  265. xfintech/serde/common/serialiserlike.py +38 -0
  266. xfintech/serde/common/tests/__init__.py +1 -0
  267. xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
  268. xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
  269. xfintech/serde/common/tests/test_class_errors_all.py +518 -0
  270. xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
  271. xfintech/serde/deserialiser/__init__.py +7 -0
  272. xfintech/serde/deserialiser/pandas.py +113 -0
  273. xfintech/serde/deserialiser/python.py +68 -0
  274. xfintech/serde/deserialiser/tests/__init__.py +1 -0
  275. xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
  276. xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
  277. xfintech/serde/serialiser/__init__.py +7 -0
  278. xfintech/serde/serialiser/pandas.py +116 -0
  279. xfintech/serde/serialiser/python.py +71 -0
  280. xfintech/serde/serialiser/tests/__init__.py +1 -0
  281. xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
  282. 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