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,603 @@
1
+ """
2
+ 描述:
3
+ - S3Connect 类的单元测试。
4
+ - 测试 S3 连接实现的所有功能和边界情况。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from unittest.mock import MagicMock, patch
10
+
11
+ import pytest
12
+
13
+ from xfintech.connect.common.connect import ConnectLike
14
+ from xfintech.connect.common.connectref import ConnectRef
15
+ from xfintech.connect.instance.s3 import S3Connect
16
+
17
+ # =============================================================================
18
+ # Initialization Tests
19
+ # =============================================================================
20
+
21
+
22
+ def test_s3connect_init_default_client():
23
+ """测试使用默认客户端初始化"""
24
+ with patch("boto3.client") as mock_client_factory:
25
+ mock_client = MagicMock()
26
+ mock_client.put_object = MagicMock()
27
+ mock_client_factory.return_value = mock_client
28
+
29
+ connect = S3Connect()
30
+
31
+ assert connect.kind == "s3"
32
+ assert connect._client is mock_client
33
+ mock_client_factory.assert_called_once_with("s3")
34
+
35
+
36
+ def test_s3connect_init_custom_client():
37
+ """测试使用自定义客户端初始化"""
38
+ mock_client = MagicMock()
39
+ mock_client.put_object = MagicMock()
40
+
41
+ connect = S3Connect(client=mock_client)
42
+
43
+ assert connect.kind == "s3"
44
+ assert connect._client is mock_client
45
+
46
+
47
+ def test_s3connect_init_without_boto3():
48
+ """测试没有 boto3 时初始化失败"""
49
+ with patch("builtins.__import__", side_effect=ImportError("No module named 'boto3'")):
50
+ with pytest.raises(ImportError) as excinfo:
51
+ S3Connect()
52
+ assert "boto3 is required" in str(excinfo.value)
53
+ assert "pip install boto3" in str(excinfo.value)
54
+
55
+
56
+ def test_s3connect_is_connectlike():
57
+ """测试 S3Connect 实现了 ConnectLike 协议"""
58
+ mock_client = MagicMock()
59
+ mock_client.put_object = MagicMock()
60
+
61
+ connect = S3Connect(client=mock_client)
62
+ assert isinstance(connect, ConnectLike)
63
+
64
+
65
+ def test_s3connect_has_docstring():
66
+ """测试 S3Connect 有文档字符串"""
67
+ assert S3Connect.__doc__ is not None
68
+ assert len(S3Connect.__doc__) > 0
69
+
70
+
71
+ # =============================================================================
72
+ # Client Validation Tests
73
+ # =============================================================================
74
+
75
+
76
+ def test_resolve_client_valid_custom_client():
77
+ """测试解析有效的自定义客户端"""
78
+ mock_client = MagicMock()
79
+ mock_client.put_object = MagicMock()
80
+
81
+ connect = S3Connect(client=mock_client)
82
+ assert connect._client is mock_client
83
+
84
+
85
+ def test_resolve_client_missing_put_object():
86
+ """测试客户端缺少 put_object 方法"""
87
+ with patch("boto3.client") as mock_client_factory:
88
+ mock_client = MagicMock(spec=[]) # No methods
89
+ mock_client_factory.return_value = mock_client
90
+
91
+ with pytest.raises(AttributeError) as excinfo:
92
+ S3Connect()
93
+ assert "missing put_object method" in str(excinfo.value)
94
+
95
+
96
+ def test_resolve_client_put_object_not_callable():
97
+ """测试 put_object 不可调用"""
98
+ with patch("boto3.client") as mock_client_factory:
99
+ mock_client = MagicMock()
100
+ mock_client.put_object = "not_callable"
101
+ mock_client_factory.return_value = mock_client
102
+
103
+ with pytest.raises(TypeError) as excinfo:
104
+ S3Connect()
105
+ assert "put_object is not callable" in str(excinfo.value)
106
+
107
+
108
+ # =============================================================================
109
+ # URI Resolution Tests
110
+ # =============================================================================
111
+
112
+
113
+ def test_resolve_s3_uri_full_uri():
114
+ """测试解析完整 S3 URI"""
115
+ mock_client = MagicMock()
116
+ mock_client.put_object = MagicMock()
117
+
118
+ connect = S3Connect(client=mock_client)
119
+ result = connect._resolve_s3_uri("s3://bucket/key")
120
+
121
+ assert result.scheme == "s3"
122
+ assert result.netloc == "bucket"
123
+ assert result.path == "/key"
124
+
125
+
126
+ def test_resolve_s3_uri_without_scheme():
127
+ """测试解析不带 scheme 的 URI"""
128
+ mock_client = MagicMock()
129
+ mock_client.put_object = MagicMock()
130
+
131
+ connect = S3Connect(client=mock_client)
132
+ result = connect._resolve_s3_uri("bucket/key")
133
+
134
+ assert result.scheme == "s3"
135
+ assert result.netloc == "bucket"
136
+ assert result.path == "/key"
137
+
138
+
139
+ def test_resolve_s3_uri_nested_path():
140
+ """测试解析嵌套路径"""
141
+ mock_client = MagicMock()
142
+ mock_client.put_object = MagicMock()
143
+
144
+ connect = S3Connect(client=mock_client)
145
+ result = connect._resolve_s3_uri("s3://bucket/path/to/key")
146
+
147
+ assert result.netloc == "bucket"
148
+ assert result.path == "/path/to/key"
149
+
150
+
151
+ def test_resolve_s3_uri_with_http_prefix():
152
+ """测试带 http 前缀的输入(会被转换)"""
153
+ mock_client = MagicMock()
154
+ mock_client.put_object = MagicMock()
155
+
156
+ connect = S3Connect(client=mock_client)
157
+ # http://bucket/key becomes s3://http://bucket/key, which is valid
158
+ result = connect._resolve_s3_uri("http://bucket/key")
159
+ assert result.scheme == "s3"
160
+ # The netloc becomes "http:" and path becomes "//bucket/key"
161
+ assert result.netloc == "http:"
162
+
163
+
164
+ def test_resolve_s3_uri_missing_bucket():
165
+ """测试缺少 bucket 的 URI"""
166
+ mock_client = MagicMock()
167
+ mock_client.put_object = MagicMock()
168
+
169
+ connect = S3Connect(client=mock_client)
170
+ with pytest.raises(ValueError) as excinfo:
171
+ connect._resolve_s3_uri("s3:///key")
172
+ assert "missing bucket" in str(excinfo.value)
173
+
174
+
175
+ def test_resolve_s3_uri_missing_key():
176
+ """测试缺少 key 的 URI"""
177
+ mock_client = MagicMock()
178
+ mock_client.put_object = MagicMock()
179
+
180
+ connect = S3Connect(client=mock_client)
181
+ with pytest.raises(ValueError) as excinfo:
182
+ connect._resolve_s3_uri("s3://bucket")
183
+ assert "missing key" in str(excinfo.value)
184
+
185
+
186
+ # =============================================================================
187
+ # Bucket Resolution Tests
188
+ # =============================================================================
189
+
190
+
191
+ def test_resolve_bucket_valid():
192
+ """测试解析有效的 bucket"""
193
+ mock_client = MagicMock()
194
+ mock_client.put_object = MagicMock()
195
+
196
+ connect = S3Connect(client=mock_client)
197
+ bucket = connect._resolve_bucket("s3://my-bucket/my-key")
198
+
199
+ assert bucket == "my-bucket"
200
+
201
+
202
+ def test_resolve_bucket_with_nested_path():
203
+ """测试解析带嵌套路径的 bucket"""
204
+ mock_client = MagicMock()
205
+ mock_client.put_object = MagicMock()
206
+
207
+ connect = S3Connect(client=mock_client)
208
+ bucket = connect._resolve_bucket("s3://my-bucket/path/to/key")
209
+
210
+ assert bucket == "my-bucket"
211
+
212
+
213
+ # =============================================================================
214
+ # Key Resolution Tests
215
+ # =============================================================================
216
+
217
+
218
+ def test_resolve_key_valid():
219
+ """测试解析有效的 key"""
220
+ mock_client = MagicMock()
221
+ mock_client.put_object = MagicMock()
222
+
223
+ connect = S3Connect(client=mock_client)
224
+ key = connect._resolve_key("s3://bucket/my-key")
225
+
226
+ assert key == "my-key"
227
+
228
+
229
+ def test_resolve_key_with_nested_path():
230
+ """测试解析嵌套路径的 key"""
231
+ mock_client = MagicMock()
232
+ mock_client.put_object = MagicMock()
233
+
234
+ connect = S3Connect(client=mock_client)
235
+ key = connect._resolve_key("s3://bucket/path/to/key.txt")
236
+
237
+ assert key == "path/to/key.txt"
238
+
239
+
240
+ def test_resolve_key_strips_leading_slash():
241
+ """测试移除 key 开头的斜杠"""
242
+ mock_client = MagicMock()
243
+ mock_client.put_object = MagicMock()
244
+
245
+ connect = S3Connect(client=mock_client)
246
+ key = connect._resolve_key("s3://bucket/key")
247
+
248
+ # Path has leading slash, should be stripped
249
+ assert not key.startswith("/")
250
+
251
+
252
+ # =============================================================================
253
+ # put_bytes Tests
254
+ # =============================================================================
255
+
256
+
257
+ def test_put_bytes_success():
258
+ """测试成功写入字节"""
259
+ mock_client = MagicMock()
260
+ mock_client.put_object = MagicMock()
261
+
262
+ connect = S3Connect(client=mock_client)
263
+ connect.put_bytes(b"test data", "s3://bucket/key")
264
+
265
+ mock_client.put_object.assert_called_once()
266
+ call_kwargs = mock_client.put_object.call_args[1]
267
+ assert call_kwargs["Bucket"] == "bucket"
268
+ assert call_kwargs["Key"] == "key"
269
+ assert call_kwargs["Body"] == b"test data"
270
+
271
+
272
+ def test_put_bytes_with_kwargs():
273
+ """测试传递额外参数"""
274
+ mock_client = MagicMock()
275
+ mock_client.put_object = MagicMock()
276
+
277
+ connect = S3Connect(client=mock_client)
278
+ connect.put_bytes(b"test data", "s3://bucket/key", ContentType="application/json", ACL="public-read")
279
+
280
+ call_kwargs = mock_client.put_object.call_args[1]
281
+ assert call_kwargs["ContentType"] == "application/json"
282
+ assert call_kwargs["ACL"] == "public-read"
283
+
284
+
285
+ def test_put_bytes_nested_path():
286
+ """测试写入嵌套路径"""
287
+ mock_client = MagicMock()
288
+ mock_client.put_object = MagicMock()
289
+
290
+ connect = S3Connect(client=mock_client)
291
+ connect.put_bytes(b"data", "s3://bucket/path/to/file.txt")
292
+
293
+ call_kwargs = mock_client.put_object.call_args[1]
294
+ assert call_kwargs["Key"] == "path/to/file.txt"
295
+
296
+
297
+ # =============================================================================
298
+ # get_bytes Tests
299
+ # =============================================================================
300
+
301
+
302
+ def test_get_bytes_success():
303
+ """测试成功读取字节"""
304
+ mock_client = MagicMock()
305
+ mock_response = {"Body": MagicMock()}
306
+ mock_response["Body"].read.return_value = b"test data"
307
+ mock_client.get_object.return_value = mock_response
308
+
309
+ connect = S3Connect(client=mock_client)
310
+ data = connect.get_bytes("s3://bucket/key")
311
+
312
+ assert data == b"test data"
313
+ mock_client.get_object.assert_called_once()
314
+ call_kwargs = mock_client.get_object.call_args[1]
315
+ assert call_kwargs["Bucket"] == "bucket"
316
+ assert call_kwargs["Key"] == "key"
317
+
318
+
319
+ def test_get_bytes_with_kwargs():
320
+ """测试传递额外参数"""
321
+ mock_client = MagicMock()
322
+ mock_response = {"Body": MagicMock()}
323
+ mock_response["Body"].read.return_value = b"data"
324
+ mock_client.get_object.return_value = mock_response
325
+
326
+ connect = S3Connect(client=mock_client)
327
+ connect.get_bytes("s3://bucket/key", VersionId="version123")
328
+
329
+ call_kwargs = mock_client.get_object.call_args[1]
330
+ assert call_kwargs["VersionId"] == "version123"
331
+
332
+
333
+ def test_get_bytes_nested_path():
334
+ """测试读取嵌套路径"""
335
+ mock_client = MagicMock()
336
+ mock_response = {"Body": MagicMock()}
337
+ mock_response["Body"].read.return_value = b"data"
338
+ mock_client.get_object.return_value = mock_response
339
+
340
+ connect = S3Connect(client=mock_client)
341
+ connect.get_bytes("s3://bucket/path/to/file.txt")
342
+
343
+ call_kwargs = mock_client.get_object.call_args[1]
344
+ assert call_kwargs["Key"] == "path/to/file.txt"
345
+
346
+
347
+ # =============================================================================
348
+ # put_object Tests
349
+ # =============================================================================
350
+
351
+
352
+ def test_put_object_returns_connectref():
353
+ """测试 put_object 返回 ConnectRef"""
354
+ mock_client = MagicMock()
355
+ mock_client.put_object = MagicMock()
356
+
357
+ connect = S3Connect(client=mock_client)
358
+ ref = connect.put_object(b"data", "s3://bucket/key")
359
+
360
+ assert isinstance(ref, ConnectRef)
361
+ assert ref.location == "s3://bucket/key"
362
+ assert ref.kind == "s3"
363
+
364
+
365
+ def test_put_object_calls_put_bytes():
366
+ """测试 put_object 调用 put_bytes"""
367
+ mock_client = MagicMock()
368
+ mock_client.put_object = MagicMock()
369
+
370
+ connect = S3Connect(client=mock_client)
371
+ connect.put_object(b"test", "s3://bucket/key")
372
+
373
+ mock_client.put_object.assert_called_once()
374
+
375
+
376
+ def test_put_object_with_kwargs():
377
+ """测试 put_object 传递 kwargs"""
378
+ mock_client = MagicMock()
379
+ mock_client.put_object = MagicMock()
380
+
381
+ connect = S3Connect(client=mock_client)
382
+ connect.put_object(b"data", "s3://bucket/key", ContentType="text/plain")
383
+
384
+ call_kwargs = mock_client.put_object.call_args[1]
385
+ assert call_kwargs["ContentType"] == "text/plain"
386
+
387
+
388
+ # =============================================================================
389
+ # get_object Tests
390
+ # =============================================================================
391
+
392
+
393
+ def test_get_object_returns_bytes():
394
+ """测试 get_object 返回字节"""
395
+ mock_client = MagicMock()
396
+ mock_response = {"Body": MagicMock()}
397
+ mock_response["Body"].read.return_value = b"test data"
398
+ mock_client.get_object.return_value = mock_response
399
+
400
+ connect = S3Connect(client=mock_client)
401
+ data = connect.get_object("s3://bucket/key")
402
+
403
+ assert isinstance(data, bytes)
404
+ assert data == b"test data"
405
+
406
+
407
+ def test_get_object_calls_get_bytes():
408
+ """测试 get_object 调用 get_bytes"""
409
+ mock_client = MagicMock()
410
+ mock_response = {"Body": MagicMock()}
411
+ mock_response["Body"].read.return_value = b"data"
412
+ mock_client.get_object.return_value = mock_response
413
+
414
+ connect = S3Connect(client=mock_client)
415
+ connect.get_object("s3://bucket/key")
416
+
417
+ mock_client.get_object.assert_called_once()
418
+
419
+
420
+ def test_get_object_with_kwargs():
421
+ """测试 get_object 传递 kwargs"""
422
+ mock_client = MagicMock()
423
+ mock_response = {"Body": MagicMock()}
424
+ mock_response["Body"].read.return_value = b"data"
425
+ mock_client.get_object.return_value = mock_response
426
+
427
+ connect = S3Connect(client=mock_client)
428
+ connect.get_object("s3://bucket/key", Range="bytes=0-100")
429
+
430
+ call_kwargs = mock_client.get_object.call_args[1]
431
+ assert call_kwargs["Range"] == "bytes=0-100"
432
+
433
+
434
+ # =============================================================================
435
+ # Integration Tests
436
+ # =============================================================================
437
+
438
+
439
+ def test_round_trip():
440
+ """测试完整的读写流程"""
441
+ mock_client = MagicMock()
442
+ mock_client.put_object = MagicMock()
443
+ mock_response = {"Body": MagicMock()}
444
+ mock_response["Body"].read.return_value = b"test data"
445
+ mock_client.get_object.return_value = mock_response
446
+
447
+ connect = S3Connect(client=mock_client)
448
+
449
+ # Write
450
+ ref = connect.put_object(b"test data", "s3://bucket/key")
451
+ assert ref.location == "s3://bucket/key"
452
+
453
+ # Read
454
+ data = connect.get_object("s3://bucket/key")
455
+ assert data == b"test data"
456
+
457
+
458
+ def test_multiple_operations():
459
+ """测试多次操作"""
460
+ mock_client = MagicMock()
461
+ mock_client.put_object = MagicMock()
462
+ mock_response = {"Body": MagicMock()}
463
+ mock_response["Body"].read.return_value = b"data"
464
+ mock_client.get_object.return_value = mock_response
465
+
466
+ connect = S3Connect(client=mock_client)
467
+
468
+ # Multiple writes
469
+ connect.put_object(b"data1", "s3://bucket/key1")
470
+ connect.put_object(b"data2", "s3://bucket/key2")
471
+
472
+ assert mock_client.put_object.call_count == 2
473
+
474
+
475
+ def test_different_buckets():
476
+ """测试不同的 bucket"""
477
+ mock_client = MagicMock()
478
+ mock_client.put_object = MagicMock()
479
+
480
+ connect = S3Connect(client=mock_client)
481
+
482
+ connect.put_object(b"data", "s3://bucket1/key")
483
+ connect.put_object(b"data", "s3://bucket2/key")
484
+
485
+ calls = mock_client.put_object.call_args_list
486
+ assert calls[0][1]["Bucket"] == "bucket1"
487
+ assert calls[1][1]["Bucket"] == "bucket2"
488
+
489
+
490
+ # =============================================================================
491
+ # Edge Cases
492
+ # =============================================================================
493
+
494
+
495
+ def test_empty_data():
496
+ """测试空数据"""
497
+ mock_client = MagicMock()
498
+ mock_client.put_object = MagicMock()
499
+
500
+ connect = S3Connect(client=mock_client)
501
+ connect.put_object(b"", "s3://bucket/key")
502
+
503
+ call_kwargs = mock_client.put_object.call_args[1]
504
+ assert call_kwargs["Body"] == b""
505
+
506
+
507
+ def test_large_data():
508
+ """测试大数据"""
509
+ mock_client = MagicMock()
510
+ mock_client.put_object = MagicMock()
511
+
512
+ large_data = b"x" * (10 * 1024 * 1024) # 10MB
513
+
514
+ connect = S3Connect(client=mock_client)
515
+ connect.put_object(large_data, "s3://bucket/key")
516
+
517
+ call_kwargs = mock_client.put_object.call_args[1]
518
+ assert len(call_kwargs["Body"]) == 10 * 1024 * 1024
519
+
520
+
521
+ def test_special_characters_in_key():
522
+ """测试 key 中的特殊字符"""
523
+ mock_client = MagicMock()
524
+ mock_client.put_object = MagicMock()
525
+
526
+ connect = S3Connect(client=mock_client)
527
+ connect.put_object(b"data", "s3://bucket/path with spaces/file.txt")
528
+
529
+ call_kwargs = mock_client.put_object.call_args[1]
530
+ assert "path with spaces" in call_kwargs["Key"]
531
+
532
+
533
+ def test_unicode_in_key():
534
+ """测试 key 中的 Unicode 字符"""
535
+ mock_client = MagicMock()
536
+ mock_client.put_object = MagicMock()
537
+
538
+ connect = S3Connect(client=mock_client)
539
+ connect.put_object(b"data", "s3://bucket/文件/数据.txt")
540
+
541
+ call_kwargs = mock_client.put_object.call_args[1]
542
+ assert "文件" in call_kwargs["Key"]
543
+
544
+
545
+ # =============================================================================
546
+ # Error Handling Tests
547
+ # =============================================================================
548
+
549
+
550
+ def test_put_bytes_client_error():
551
+ """测试客户端错误"""
552
+ mock_client = MagicMock()
553
+ mock_client.put_object.side_effect = Exception("S3 Error")
554
+
555
+ connect = S3Connect(client=mock_client)
556
+
557
+ with pytest.raises(Exception) as excinfo:
558
+ connect.put_object(b"data", "s3://bucket/key")
559
+ assert "S3 Error" in str(excinfo.value)
560
+
561
+
562
+ def test_get_bytes_client_error():
563
+ """测试读取时客户端错误"""
564
+ mock_client = MagicMock()
565
+ mock_client.get_object.side_effect = Exception("Not Found")
566
+
567
+ connect = S3Connect(client=mock_client)
568
+
569
+ with pytest.raises(Exception) as excinfo:
570
+ connect.get_object("s3://bucket/nonexistent")
571
+ assert "Not Found" in str(excinfo.value)
572
+
573
+
574
+ # =============================================================================
575
+ # Type Tests
576
+ # =============================================================================
577
+
578
+
579
+ def test_returns_correct_types():
580
+ """测试返回正确的类型"""
581
+ mock_client = MagicMock()
582
+ mock_client.put_object = MagicMock()
583
+ mock_response = {"Body": MagicMock()}
584
+ mock_response["Body"].read.return_value = b"data"
585
+ mock_client.get_object.return_value = mock_response
586
+
587
+ connect = S3Connect(client=mock_client)
588
+
589
+ # put_object returns ConnectRef
590
+ ref = connect.put_object(b"data", "s3://bucket/key")
591
+ assert isinstance(ref, ConnectRef)
592
+
593
+ # get_object returns bytes
594
+ data = connect.get_object("s3://bucket/key")
595
+ assert isinstance(data, bytes)
596
+
597
+ # put_bytes returns None
598
+ result = connect.put_bytes(b"data", "s3://bucket/key2")
599
+ assert result is None
600
+
601
+ # get_bytes returns bytes
602
+ data = connect.get_bytes("s3://bucket/key")
603
+ assert isinstance(data, bytes)
@@ -0,0 +1,20 @@
1
+ """
2
+ Data module for xfintech.
3
+
4
+ This module provides the core data acquisition and processing infrastructure:
5
+
6
+ Submodules:
7
+ - common: Common utilities (Cache, Retry, Metric, Params, Paginate, Coolant)
8
+ - job: Job system with registry and error handling
9
+ - relay: Relay client for remote API access
10
+ - source: Data source implementations (tushare, baostock)
11
+ """
12
+
13
+ from . import common, job, relay, source
14
+
15
+ __all__ = [
16
+ "common",
17
+ "job",
18
+ "relay",
19
+ "source",
20
+ ]
@@ -0,0 +1,15 @@
1
+ from .cache import Cache
2
+ from .coolant import Coolant
3
+ from .metric import Metric
4
+ from .paginate import Paginate
5
+ from .params import Params
6
+ from .retry import Retry
7
+
8
+ __all__ = [
9
+ "Cache",
10
+ "Metric",
11
+ "Params",
12
+ "Coolant",
13
+ "Paginate",
14
+ "Retry",
15
+ ]