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,705 @@
1
+ import time
2
+
3
+ import pandas as pd
4
+ import pytest
5
+
6
+ from xfintech.data.common.metric import Metric
7
+
8
+ # ==================== Metric Initialization Tests ====================
9
+
10
+
11
+ def test_metric_init():
12
+ """Test Metric initialization creates correct default attributes"""
13
+ metric = Metric()
14
+
15
+ assert metric.start_at is None
16
+ assert metric.finish_at is None
17
+ assert isinstance(metric.marks, dict)
18
+ assert len(metric.marks) == 0
19
+ assert isinstance(metric.errors, list)
20
+ assert len(metric.errors) == 0
21
+
22
+
23
+ def test_metric_init_multiple_instances():
24
+ """Test that multiple Metric instances are independent"""
25
+ m1 = Metric()
26
+ m2 = Metric()
27
+
28
+ m1.start()
29
+ m1.mark("test")
30
+
31
+ assert m1.start_at is not None
32
+ assert m2.start_at is None
33
+ assert "test" in m1.marks
34
+ assert "test" not in m2.marks
35
+
36
+
37
+ # ==================== Start/Finish Tests ====================
38
+
39
+
40
+ def test_metric_start():
41
+ """Test start() records start timestamp"""
42
+ metric = Metric()
43
+ before = pd.Timestamp.now()
44
+
45
+ metric.start()
46
+
47
+ after = pd.Timestamp.now()
48
+ assert metric.start_at is not None
49
+ assert isinstance(metric.start_at, pd.Timestamp)
50
+ assert before <= metric.start_at <= after
51
+
52
+
53
+ def test_metric_finish():
54
+ """Test finish() records finish timestamp"""
55
+ metric = Metric()
56
+ metric.start()
57
+ time.sleep(0.01)
58
+
59
+ metric.finish()
60
+
61
+ assert metric.finish_at is not None
62
+ assert isinstance(metric.finish_at, pd.Timestamp)
63
+ assert metric.finish_at > metric.start_at
64
+
65
+
66
+ def test_metric_start_multiple_times():
67
+ """Test calling start() multiple times updates start_at"""
68
+ metric = Metric()
69
+
70
+ metric.start()
71
+ first_start = metric.start_at
72
+ time.sleep(0.01)
73
+
74
+ metric.start()
75
+ second_start = metric.start_at
76
+
77
+ assert second_start > first_start
78
+
79
+
80
+ def test_metric_finish_without_start():
81
+ """Test finish() can be called without start()"""
82
+ metric = Metric()
83
+
84
+ metric.finish()
85
+
86
+ assert metric.start_at is None
87
+ assert metric.finish_at is not None
88
+
89
+
90
+ # ==================== Duration Property Tests ====================
91
+
92
+
93
+ def test_metric_duration_not_started():
94
+ """Test duration returns 0.0 when not started"""
95
+ metric = Metric()
96
+
97
+ assert metric.duration == 0.0
98
+
99
+
100
+ def test_metric_duration_started_not_finished():
101
+ """Test duration calculates time from start to now when not finished"""
102
+ metric = Metric()
103
+ metric.start()
104
+ time.sleep(0.05)
105
+
106
+ duration = metric.duration
107
+
108
+ assert duration >= 0.05
109
+ assert duration < 0.2 # reasonable upper bound
110
+
111
+
112
+ def test_metric_duration_finished():
113
+ """Test duration calculates time from start to finish"""
114
+ metric = Metric()
115
+ metric.start()
116
+ time.sleep(0.05)
117
+ metric.finish()
118
+
119
+ duration = metric.duration
120
+
121
+ assert duration >= 0.05
122
+ assert duration < 0.2
123
+ # Duration should be stable after finish
124
+ time.sleep(0.01)
125
+ assert metric.duration == duration
126
+
127
+
128
+ def test_metric_duration_precision():
129
+ """Test duration calculation precision"""
130
+ metric = Metric()
131
+ metric.start()
132
+ metric.finish()
133
+
134
+ duration = metric.duration
135
+
136
+ assert isinstance(duration, float)
137
+ assert duration >= 0
138
+
139
+
140
+ # ==================== Mark Tests ====================
141
+
142
+
143
+ def test_metric_mark():
144
+ """Test mark() records a named timestamp"""
145
+ metric = Metric()
146
+ before = pd.Timestamp.now()
147
+
148
+ metric.mark("checkpoint")
149
+
150
+ after = pd.Timestamp.now()
151
+ assert "checkpoint" in metric.marks
152
+ assert isinstance(metric.marks["checkpoint"], pd.Timestamp)
153
+ assert before <= metric.marks["checkpoint"] <= after
154
+
155
+
156
+ def test_metric_mark_multiple():
157
+ """Test marking multiple checkpoints"""
158
+ metric = Metric()
159
+
160
+ metric.mark("step1")
161
+ time.sleep(0.01)
162
+ metric.mark("step2")
163
+ time.sleep(0.01)
164
+ metric.mark("step3")
165
+
166
+ assert len(metric.marks) == 3
167
+ assert "step1" in metric.marks
168
+ assert "step2" in metric.marks
169
+ assert "step3" in metric.marks
170
+ assert metric.marks["step1"] < metric.marks["step2"] < metric.marks["step3"]
171
+
172
+
173
+ def test_metric_mark_overwrite():
174
+ """Test marking with same name overwrites previous mark"""
175
+ metric = Metric()
176
+
177
+ metric.mark("test")
178
+ first_mark = metric.marks["test"]
179
+ time.sleep(0.01)
180
+
181
+ metric.mark("test")
182
+ second_mark = metric.marks["test"]
183
+
184
+ assert len(metric.marks) == 1
185
+ assert second_mark > first_mark
186
+
187
+
188
+ def test_metric_mark_without_start():
189
+ """Test mark() works without calling start()"""
190
+ metric = Metric()
191
+
192
+ metric.mark("standalone")
193
+
194
+ assert "standalone" in metric.marks
195
+ assert metric.start_at is None
196
+
197
+
198
+ # ==================== Reset Tests ====================
199
+
200
+
201
+ def test_metric_reset():
202
+ """Test reset() clears all state"""
203
+ metric = Metric()
204
+ metric.start()
205
+ metric.mark("test")
206
+ metric.errors = ["error1"]
207
+ metric.finish()
208
+
209
+ metric.reset()
210
+
211
+ assert metric.start_at is None
212
+ assert metric.finish_at is None
213
+ assert len(metric.marks) == 0
214
+ assert len(metric.errors) == 0
215
+
216
+
217
+ def test_metric_reset_empty():
218
+ """Test reset() on already empty metric"""
219
+ metric = Metric()
220
+
221
+ metric.reset()
222
+
223
+ assert metric.start_at is None
224
+ assert metric.finish_at is None
225
+ assert len(metric.marks) == 0
226
+ assert len(metric.errors) == 0
227
+
228
+
229
+ def test_metric_reset_reusability():
230
+ """Test metric can be reused after reset"""
231
+ metric = Metric()
232
+ metric.start()
233
+ metric.finish()
234
+ first_duration = metric.duration
235
+
236
+ metric.reset()
237
+ metric.start()
238
+ time.sleep(0.01)
239
+ metric.finish()
240
+ second_duration = metric.duration
241
+
242
+ assert first_duration != second_duration
243
+ assert metric.duration > 0
244
+
245
+
246
+ # ==================== ISO Format Tests ====================
247
+
248
+
249
+ def test_metric_get_start_iso_none():
250
+ """Test get_start_iso() returns None when not started"""
251
+ metric = Metric()
252
+
253
+ assert metric.get_start_iso() is None
254
+
255
+
256
+ def test_metric_get_start_iso_format():
257
+ """Test get_start_iso() returns valid ISO format string"""
258
+ metric = Metric()
259
+ metric.start()
260
+
261
+ iso_str = metric.get_start_iso()
262
+
263
+ assert iso_str is not None
264
+ assert isinstance(iso_str, str)
265
+ # Should be able to parse back
266
+ parsed = pd.Timestamp(iso_str)
267
+ assert isinstance(parsed, pd.Timestamp)
268
+
269
+
270
+ def test_metric_get_finish_iso_none():
271
+ """Test get_finish_iso() returns None when not finished"""
272
+ metric = Metric()
273
+
274
+ assert metric.get_finish_iso() is None
275
+
276
+
277
+ def test_metric_get_finish_iso_format():
278
+ """Test get_finish_iso() returns valid ISO format string"""
279
+ metric = Metric()
280
+ metric.finish()
281
+
282
+ iso_str = metric.get_finish_iso()
283
+
284
+ assert iso_str is not None
285
+ assert isinstance(iso_str, str)
286
+ # Should be able to parse back
287
+ parsed = pd.Timestamp(iso_str)
288
+ assert isinstance(parsed, pd.Timestamp)
289
+
290
+
291
+ def test_metric_get_mark_iso_empty():
292
+ """Test get_mark_iso() returns empty dict when no marks"""
293
+ metric = Metric()
294
+
295
+ marks_iso = metric.get_mark_iso()
296
+
297
+ assert isinstance(marks_iso, dict)
298
+ assert len(marks_iso) == 0
299
+
300
+
301
+ def test_metric_get_mark_iso_format():
302
+ """Test get_mark_iso() returns marks in ISO format"""
303
+ metric = Metric()
304
+ metric.mark("test1")
305
+ metric.mark("test2")
306
+
307
+ marks_iso = metric.get_mark_iso()
308
+
309
+ assert len(marks_iso) == 2
310
+ assert "test1" in marks_iso
311
+ assert "test2" in marks_iso
312
+ assert isinstance(marks_iso["test1"], str)
313
+ assert isinstance(marks_iso["test2"], str)
314
+ # Should be able to parse back
315
+ parsed1 = pd.Timestamp(marks_iso["test1"])
316
+ parsed2 = pd.Timestamp(marks_iso["test2"])
317
+ assert isinstance(parsed1, pd.Timestamp)
318
+ assert isinstance(parsed2, pd.Timestamp)
319
+
320
+
321
+ # ==================== Context Manager Tests ====================
322
+
323
+
324
+ def test_metric_context_manager_success():
325
+ """Test Metric as context manager with successful execution"""
326
+ with Metric() as metric:
327
+ assert metric.start_at is not None
328
+ time.sleep(0.01)
329
+ metric.mark("checkpoint")
330
+
331
+ assert metric.finish_at is not None
332
+ assert metric.duration > 0
333
+ assert "checkpoint" in metric.marks
334
+ assert len(metric.errors) == 0
335
+
336
+
337
+ def test_metric_context_manager_returns_self():
338
+ """Test context manager returns the metric instance"""
339
+ metric = Metric()
340
+
341
+ with metric as m:
342
+ assert m is metric
343
+
344
+
345
+ def test_metric_context_manager_resets_on_enter():
346
+ """Test context manager resets state on __enter__"""
347
+ metric = Metric()
348
+ metric.start()
349
+ metric.mark("old")
350
+ metric.errors = ["old_error"]
351
+
352
+ with metric as m:
353
+ assert m.start_at is not None
354
+ assert len(m.marks) == 0
355
+ assert len(m.errors) == 0
356
+
357
+
358
+ def test_metric_context_manager_exception_capture():
359
+ """Test context manager captures exceptions"""
360
+ try:
361
+ with Metric() as metric:
362
+ raise ValueError("Test exception")
363
+ except ValueError:
364
+ pass # Exception propagated but captured in metric
365
+
366
+ # Exception should be captured in errors
367
+ assert len(metric.errors) > 0
368
+ assert any("ValueError" in error for error in metric.errors)
369
+ assert any("Test exception" in error for error in metric.errors)
370
+
371
+
372
+ def test_metric_context_manager_exception_propagation():
373
+ """Test context manager doesn't suppress exceptions"""
374
+ with pytest.raises(ValueError, match="Test error"):
375
+ with Metric():
376
+ raise ValueError("Test error")
377
+
378
+
379
+ def test_metric_context_manager_exception_traceback():
380
+ """Test context manager captures full traceback"""
381
+ try:
382
+ with Metric() as metric:
383
+
384
+ def inner_function():
385
+ raise RuntimeError("Inner error")
386
+
387
+ inner_function()
388
+ except RuntimeError:
389
+ pass
390
+
391
+ assert len(metric.errors) > 0
392
+ # Should contain traceback information
393
+ full_traceback = "\n".join(metric.errors)
394
+ assert "RuntimeError" in full_traceback
395
+ assert "Inner error" in full_traceback
396
+ assert "inner_function" in full_traceback
397
+
398
+
399
+ def test_metric_context_manager_no_exception():
400
+ """Test context manager with no exception"""
401
+ with Metric() as metric:
402
+ time.sleep(0.01)
403
+
404
+ assert len(metric.errors) == 0
405
+ assert metric.finish_at is not None
406
+
407
+
408
+ # ==================== Describe Method Tests ====================
409
+
410
+
411
+ def test_metric_describe_empty():
412
+ """Test describe() with no data"""
413
+ metric = Metric()
414
+
415
+ result = metric.describe()
416
+
417
+ assert isinstance(result, dict)
418
+ assert "duration" in result
419
+ assert result["duration"] == 0.0
420
+ # Should not include None fields
421
+ assert "started_at" not in result
422
+ assert "finished_at" not in result
423
+ assert "errors" not in result
424
+ assert "marks" not in result
425
+
426
+
427
+ def test_metric_describe_started():
428
+ """Test describe() with only start"""
429
+ metric = Metric()
430
+ metric.start()
431
+
432
+ result = metric.describe()
433
+
434
+ assert "started_at" in result
435
+ assert "duration" in result
436
+ assert result["duration"] > 0
437
+ assert "finished_at" not in result
438
+
439
+
440
+ def test_metric_describe_finished():
441
+ """Test describe() with start and finish"""
442
+ metric = Metric()
443
+ metric.start()
444
+ time.sleep(0.01)
445
+ metric.finish()
446
+
447
+ result = metric.describe()
448
+
449
+ assert "started_at" in result
450
+ assert "finished_at" in result
451
+ assert "duration" in result
452
+ assert result["duration"] > 0
453
+
454
+
455
+ def test_metric_describe_with_marks():
456
+ """Test describe() includes marks"""
457
+ metric = Metric()
458
+ metric.start()
459
+ metric.mark("checkpoint")
460
+ metric.finish()
461
+
462
+ result = metric.describe()
463
+
464
+ assert "marks" in result
465
+ assert isinstance(result["marks"], dict)
466
+ assert "checkpoint" in result["marks"]
467
+
468
+
469
+ def test_metric_describe_with_errors():
470
+ """Test describe() includes errors"""
471
+ metric = Metric()
472
+ try:
473
+ with metric:
474
+ raise ValueError("Test")
475
+ except ValueError:
476
+ pass
477
+
478
+ result = metric.describe()
479
+
480
+ assert "errors" in result
481
+ assert isinstance(result["errors"], list)
482
+ assert len(result["errors"]) > 0
483
+
484
+
485
+ def test_metric_describe_complete():
486
+ """Test describe() with all fields populated"""
487
+ metric = Metric()
488
+ try:
489
+ with metric:
490
+ metric.mark("step1")
491
+ raise Exception("test error")
492
+ except Exception:
493
+ pass
494
+
495
+ result = metric.describe()
496
+
497
+ assert "started_at" in result
498
+ assert "finished_at" in result
499
+ assert "duration" in result
500
+ assert "marks" in result
501
+ assert "errors" in result
502
+
503
+
504
+ # ==================== To Dict Method Tests ====================
505
+
506
+
507
+ def test_metric_to_dict_empty():
508
+ """Test to_dict() with no data includes all fields"""
509
+ metric = Metric()
510
+
511
+ result = metric.to_dict()
512
+
513
+ assert isinstance(result, dict)
514
+ assert "started_at" in result
515
+ assert "finished_at" in result
516
+ assert "duration" in result
517
+ assert "errors" in result
518
+ assert "marks" in result
519
+ # Values should be None/empty but keys present
520
+ assert result["started_at"] is None
521
+ assert result["finished_at"] is None
522
+ assert result["duration"] == 0.0
523
+ assert result["errors"] == []
524
+ assert result["marks"] == {}
525
+
526
+
527
+ def test_metric_to_dict_with_data():
528
+ """Test to_dict() returns complete dictionary"""
529
+ metric = Metric()
530
+ metric.start()
531
+ metric.mark("test")
532
+ time.sleep(0.01)
533
+ metric.finish()
534
+
535
+ result = metric.to_dict()
536
+
537
+ assert result["started_at"] is not None
538
+ assert result["finished_at"] is not None
539
+ assert result["duration"] > 0
540
+ assert len(result["marks"]) == 1
541
+ assert "test" in result["marks"]
542
+
543
+
544
+ def test_metric_to_dict_vs_describe():
545
+ """Test to_dict() includes None values while describe() omits them"""
546
+ metric = Metric()
547
+
548
+ to_dict_result = metric.to_dict()
549
+ describe_result = metric.describe()
550
+
551
+ # to_dict should have None values
552
+ assert "started_at" in to_dict_result
553
+ assert to_dict_result["started_at"] is None
554
+
555
+ # describe should not have None values
556
+ assert "started_at" not in describe_result
557
+
558
+
559
+ def test_metric_to_dict_structure():
560
+ """Test to_dict() returns expected structure"""
561
+ metric = Metric()
562
+ metric.mark("test")
563
+
564
+ result = metric.to_dict()
565
+
566
+ # Result should have correct structure
567
+ assert "marks" in result
568
+ assert "errors" in result
569
+ assert "test" in result["marks"]
570
+ assert isinstance(result["errors"], list)
571
+
572
+ # Note: to_dict returns references to internal state, not deep copies
573
+ # This is a known behavior where modifying result affects metric
574
+
575
+
576
+ # ==================== Integration Tests ====================
577
+
578
+
579
+ def test_metric_full_workflow():
580
+ """Test complete metric workflow"""
581
+ metric = Metric()
582
+
583
+ # Start timing
584
+ metric.start()
585
+ assert metric.start_at is not None
586
+
587
+ # Do some work with marks
588
+ time.sleep(0.01)
589
+ metric.mark("phase1")
590
+
591
+ time.sleep(0.01)
592
+ metric.mark("phase2")
593
+
594
+ time.sleep(0.01)
595
+ metric.mark("phase3")
596
+
597
+ # Finish
598
+ metric.finish()
599
+
600
+ # Verify results
601
+ assert metric.duration >= 0.03
602
+ assert len(metric.marks) == 3
603
+ assert len(metric.errors) == 0
604
+
605
+ # Check ordering
606
+ assert metric.start_at < metric.marks["phase1"]
607
+ assert metric.marks["phase1"] < metric.marks["phase2"]
608
+ assert metric.marks["phase2"] < metric.marks["phase3"]
609
+ assert metric.marks["phase3"] < metric.finish_at
610
+
611
+
612
+ def test_metric_context_manager_full_workflow():
613
+ """Test complete metric workflow with context manager"""
614
+ with Metric() as metric:
615
+ time.sleep(0.01)
616
+ metric.mark("step1")
617
+
618
+ time.sleep(0.01)
619
+ metric.mark("step2")
620
+
621
+ time.sleep(0.01)
622
+
623
+ # Verify
624
+ assert metric.duration >= 0.03
625
+ assert len(metric.marks) == 2
626
+ assert len(metric.errors) == 0
627
+
628
+ # Check data availability
629
+ describe = metric.describe()
630
+ assert "started_at" in describe
631
+ assert "finished_at" in describe
632
+ assert "marks" in describe
633
+
634
+
635
+ def test_metric_reuse_after_context():
636
+ """Test metric can be reused after context manager"""
637
+ metric = Metric()
638
+
639
+ # First use
640
+ with metric:
641
+ metric.mark("first")
642
+
643
+ first_duration = metric.duration
644
+
645
+ # Second use
646
+ with metric:
647
+ time.sleep(0.02)
648
+ metric.mark("second")
649
+
650
+ second_duration = metric.duration
651
+
652
+ # Second use should have different timing
653
+ assert second_duration != first_duration
654
+ assert "first" not in metric.marks
655
+ assert "second" in metric.marks
656
+
657
+
658
+ def test_metric_error_handling_workflow():
659
+ """Test metric properly handles errors in workflow"""
660
+ results = []
661
+
662
+ for i in range(3):
663
+ m = Metric()
664
+ try:
665
+ with m:
666
+ m.mark(f"attempt_{i}")
667
+ if i < 2:
668
+ raise ValueError(f"Error {i}")
669
+ except ValueError:
670
+ pass
671
+
672
+ results.append({"attempt": i, "duration": m.duration, "has_errors": len(m.errors) > 0})
673
+
674
+ # First two should have errors
675
+ assert results[0]["has_errors"]
676
+ assert results[1]["has_errors"]
677
+ assert not results[2]["has_errors"]
678
+
679
+
680
+ def test_metric_performance_tracking():
681
+ """Test metric for performance tracking scenario"""
682
+ metric = Metric()
683
+ metric.start()
684
+
685
+ # Simulate different stages
686
+ stages = ["load", "process", "validate", "save"]
687
+
688
+ for stage in stages:
689
+ time.sleep(0.01)
690
+ metric.mark(stage)
691
+
692
+ metric.finish()
693
+
694
+ # Verify all stages recorded
695
+ assert len(metric.marks) == len(stages)
696
+ for stage in stages:
697
+ assert stage in metric.marks
698
+
699
+ # Verify total duration
700
+ assert metric.duration >= 0.04
701
+
702
+ # Get report
703
+ report = metric.describe()
704
+ assert "marks" in report
705
+ assert len(report["marks"]) == len(stages)