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,891 @@
1
+ """
2
+ Test suite for Params class
3
+ Tests cover initialization, attribute access, serialization, and data conversion
4
+ """
5
+
6
+ from datetime import datetime
7
+
8
+ import pytest
9
+
10
+ from xfintech.data.common.params import Params
11
+
12
+ # ============================================================================
13
+ # Initialization Tests
14
+ # ============================================================================
15
+
16
+
17
+ def test_params_init_empty():
18
+ """Test Params initialization with no arguments"""
19
+ params = Params()
20
+ assert params.to_dict() == {}
21
+
22
+
23
+ def test_params_init_single_kwarg():
24
+ """Test Params initialization with single keyword argument"""
25
+ params = Params(name="test")
26
+ assert params.name == "test"
27
+ assert params.to_dict() == {"name": "test"}
28
+
29
+
30
+ def test_params_init_multiple_kwargs():
31
+ """Test Params initialization with multiple keyword arguments"""
32
+ params = Params(symbol="AAPL", price=150.5, quantity=100)
33
+ assert params.symbol == "AAPL"
34
+ assert params.price == 150.5
35
+ assert params.quantity == 100
36
+
37
+
38
+ def test_params_init_with_various_types():
39
+ """Test Params initialization with various data types"""
40
+ params = Params(
41
+ string="text",
42
+ integer=42,
43
+ floating=3.14,
44
+ boolean=True,
45
+ none_value=None,
46
+ list_value=[1, 2, 3],
47
+ dict_value={"key": "value"},
48
+ )
49
+ assert params.string == "text"
50
+ assert params.integer == 42
51
+ assert params.floating == 3.14
52
+ assert params.boolean is True
53
+ assert params.none_value is None
54
+ assert params.list_value == [1, 2, 3]
55
+ assert params.dict_value == {"key": "value"}
56
+
57
+
58
+ def test_params_init_with_datetime():
59
+ """Test Params initialization with datetime object"""
60
+ dt = datetime(2024, 1, 15, 10, 30, 0)
61
+ params = Params(timestamp=dt)
62
+ assert params.timestamp == dt
63
+
64
+
65
+ def test_params_init_with_nested_params():
66
+ """Test Params initialization with nested Params object"""
67
+ nested = Params(inner="value")
68
+ params = Params(outer="test", nested=nested)
69
+ assert params.outer == "test"
70
+ assert isinstance(params.nested, Params)
71
+ assert params.nested.inner == "value"
72
+
73
+
74
+ # ============================================================================
75
+ # Attribute Access Tests
76
+ # ============================================================================
77
+
78
+
79
+ def test_params_attribute_access():
80
+ """Test direct attribute access"""
81
+ params = Params(key="value")
82
+ assert params.key == "value"
83
+
84
+
85
+ def test_params_attribute_assignment():
86
+ """Test direct attribute assignment"""
87
+ params = Params()
88
+ params.new_key = "new_value"
89
+ assert params.new_key == "new_value"
90
+ assert params.to_dict() == {"new_key": "new_value"}
91
+
92
+
93
+ def test_params_attribute_modification():
94
+ """Test modifying existing attribute"""
95
+ params = Params(value=10)
96
+ params.value = 20
97
+ assert params.value == 20
98
+
99
+
100
+ def test_params_attribute_error():
101
+ """Test accessing non-existent attribute raises AttributeError"""
102
+ params = Params()
103
+ with pytest.raises(AttributeError):
104
+ _ = params.nonexistent
105
+
106
+
107
+ # ============================================================================
108
+ # Contains Tests (__contains__)
109
+ # ============================================================================
110
+
111
+
112
+ def test_params_contains_true():
113
+ """Test __contains__ returns True for existing attribute"""
114
+ params = Params(key="value")
115
+ assert "key" in params
116
+
117
+
118
+ def test_params_contains_false():
119
+ """Test __contains__ returns False for non-existent attribute"""
120
+ params = Params(key="value")
121
+ assert "other" not in params
122
+
123
+
124
+ def test_params_contains_after_set():
125
+ """Test __contains__ after setting attribute"""
126
+ params = Params()
127
+ assert "new_attr" not in params
128
+ params.new_attr = "value"
129
+ assert "new_attr" in params
130
+
131
+
132
+ def test_params_contains_private_attribute():
133
+ """Test __contains__ with private attributes"""
134
+ params = Params()
135
+ params._private = "value"
136
+ assert "_private" in params
137
+
138
+
139
+ # ============================================================================
140
+ # String Representation Tests
141
+ # ============================================================================
142
+
143
+
144
+ def test_params_str_empty():
145
+ """Test __str__ with empty Params"""
146
+ params = Params()
147
+ assert str(params) == "{}"
148
+
149
+
150
+ def test_params_str_with_data():
151
+ """Test __str__ with data"""
152
+ params = Params(key="value", number=42)
153
+ result = str(params)
154
+ # Could be in different order
155
+ assert "key" in result
156
+ assert "value" in result
157
+ assert "number" in result
158
+ assert "42" in result
159
+
160
+
161
+ def test_params_repr_empty():
162
+ """Test __repr__ with empty Params"""
163
+ params = Params()
164
+ assert repr(params) == "Params({})"
165
+
166
+
167
+ def test_params_repr_with_data():
168
+ """Test __repr__ with data"""
169
+ params = Params(key="value")
170
+ result = repr(params)
171
+ assert result.startswith("Params(")
172
+ assert "key" in result
173
+ assert "value" in result
174
+
175
+
176
+ # ============================================================================
177
+ # Get Method Tests
178
+ # ============================================================================
179
+
180
+
181
+ def test_params_get_existing_key():
182
+ """Test get method with existing key"""
183
+ params = Params(key="value")
184
+ assert params.get("key") == "value"
185
+
186
+
187
+ def test_params_get_nonexistent_key():
188
+ """Test get method with non-existent key returns None"""
189
+ params = Params()
190
+ assert params.get("nonexistent") is None
191
+
192
+
193
+ def test_params_get_with_default():
194
+ """Test get method with default value"""
195
+ params = Params()
196
+ assert params.get("missing", "default") == "default"
197
+
198
+
199
+ def test_params_get_existing_key_ignores_default():
200
+ """Test get method with existing key ignores default"""
201
+ params = Params(key="value")
202
+ assert params.get("key", "default") == "value"
203
+
204
+
205
+ def test_params_get_none_value():
206
+ """Test get method with None as actual value"""
207
+ params = Params(key=None)
208
+ assert params.get("key") is None
209
+ assert params.get("key", "default") is None
210
+
211
+
212
+ # ============================================================================
213
+ # Set Method Tests
214
+ # ============================================================================
215
+
216
+
217
+ def test_params_set_new_attribute():
218
+ """Test set method creates new attribute"""
219
+ params = Params()
220
+ params.set("key", "value")
221
+ assert params.key == "value"
222
+
223
+
224
+ def test_params_set_existing_attribute():
225
+ """Test set method overwrites existing attribute"""
226
+ params = Params(key="old")
227
+ params.set("key", "new")
228
+ assert params.key == "new"
229
+
230
+
231
+ def test_params_set_various_types():
232
+ """Test set method with various types"""
233
+ params = Params()
234
+ params.set("string", "text")
235
+ params.set("integer", 42)
236
+ params.set("list", [1, 2, 3])
237
+
238
+ assert params.string == "text"
239
+ assert params.integer == 42
240
+ assert params.list == [1, 2, 3]
241
+
242
+
243
+ def test_params_set_none():
244
+ """Test set method with None value"""
245
+ params = Params()
246
+ params.set("key", None)
247
+ assert params.key is None
248
+
249
+
250
+ # ============================================================================
251
+ # From Dict Tests
252
+ # ============================================================================
253
+
254
+
255
+ def test_params_from_dict_empty():
256
+ """Test from_dict with empty dictionary"""
257
+ params = Params.from_dict({})
258
+ assert params.to_dict() == {}
259
+
260
+
261
+ def test_params_from_dict_single_item():
262
+ """Test from_dict with single item"""
263
+ params = Params.from_dict({"key": "value"})
264
+ assert params.key == "value"
265
+
266
+
267
+ def test_params_from_dict_multiple_items():
268
+ """Test from_dict with multiple items"""
269
+ data = {"symbol": "AAPL", "price": 150.5, "quantity": 100}
270
+ params = Params.from_dict(data)
271
+ assert params.symbol == "AAPL"
272
+ assert params.price == 150.5
273
+ assert params.quantity == 100
274
+
275
+
276
+ def test_params_from_dict_with_params_instance():
277
+ """Test from_dict with Params instance returns same instance"""
278
+ original = Params(key="value")
279
+ result = Params.from_dict(original)
280
+ assert result is original
281
+
282
+
283
+ def test_params_from_dict_with_nested_dict():
284
+ """Test from_dict with nested dictionary"""
285
+ data = {"outer": "value", "inner": {"nested": "data"}}
286
+ params = Params.from_dict(data)
287
+ assert params.outer == "value"
288
+ assert params.inner == {"nested": "data"}
289
+
290
+
291
+ # ============================================================================
292
+ # Ensure Serialisable Tests
293
+ # ============================================================================
294
+
295
+
296
+ def test_params_ensure_serialisable_int():
297
+ """Test ensure_serialisable with integer"""
298
+ assert Params.ensure_serialisable(42) == 42
299
+
300
+
301
+ def test_params_ensure_serialisable_float():
302
+ """Test ensure_serialisable with float"""
303
+ assert Params.ensure_serialisable(3.14) == 3.14
304
+
305
+
306
+ def test_params_ensure_serialisable_string():
307
+ """Test ensure_serialisable with string"""
308
+ assert Params.ensure_serialisable("text") == "text"
309
+
310
+
311
+ def test_params_ensure_serialisable_bool():
312
+ """Test ensure_serialisable with boolean"""
313
+ assert Params.ensure_serialisable(True) is True
314
+ assert Params.ensure_serialisable(False) is False
315
+
316
+
317
+ def test_params_ensure_serialisable_none():
318
+ """Test ensure_serialisable with None"""
319
+ assert Params.ensure_serialisable(None) is None
320
+
321
+
322
+ def test_params_ensure_serialisable_datetime():
323
+ """Test ensure_serialisable with datetime converts to string"""
324
+ dt = datetime(2024, 1, 15)
325
+ result = Params.ensure_serialisable(dt)
326
+ assert result == "2024-01-15"
327
+ assert isinstance(result, str)
328
+
329
+
330
+ def test_params_ensure_serialisable_params_instance():
331
+ """Test ensure_serialisable with Params instance converts to dict"""
332
+ params = Params(key="value", number=42)
333
+ result = Params.ensure_serialisable(params)
334
+ assert isinstance(result, dict)
335
+ assert result["key"] == "value"
336
+ assert result["number"] == 42
337
+
338
+
339
+ def test_params_ensure_serialisable_dict():
340
+ """Test ensure_serialisable with dictionary"""
341
+ data = {"key": "value", "number": 42}
342
+ result = Params.ensure_serialisable(data)
343
+ assert result == {"key": "value", "number": 42}
344
+
345
+
346
+ def test_params_ensure_serialisable_dict_with_datetime():
347
+ """Test ensure_serialisable with dict containing datetime"""
348
+ dt = datetime(2024, 1, 15)
349
+ data = {"date": dt, "value": 100}
350
+ result = Params.ensure_serialisable(data)
351
+ assert result == {"date": "2024-01-15", "value": 100}
352
+
353
+
354
+ def test_params_ensure_serialisable_list():
355
+ """Test ensure_serialisable with list"""
356
+ data = [1, 2, 3]
357
+ result = Params.ensure_serialisable(data)
358
+ assert result == [1, 2, 3]
359
+
360
+
361
+ def test_params_ensure_serialisable_list_with_datetime():
362
+ """Test ensure_serialisable with list containing datetime"""
363
+ dt = datetime(2024, 1, 15)
364
+ data = [dt, "text", 42]
365
+ result = Params.ensure_serialisable(data)
366
+ assert result == ["2024-01-15", "text", 42]
367
+
368
+
369
+ def test_params_ensure_serialisable_nested_dict():
370
+ """Test ensure_serialisable with nested dictionary"""
371
+ dt = datetime(2024, 1, 15)
372
+ data = {"outer": {"inner": {"date": dt, "value": 100}}}
373
+ result = Params.ensure_serialisable(data)
374
+ assert result["outer"]["inner"]["date"] == "2024-01-15"
375
+ assert result["outer"]["inner"]["value"] == 100
376
+
377
+
378
+ def test_params_ensure_serialisable_nested_list():
379
+ """Test ensure_serialisable with nested list"""
380
+ dt = datetime(2024, 1, 15)
381
+ data = [[dt, 1], [2, "text"]]
382
+ result = Params.ensure_serialisable(data)
383
+ assert result == [["2024-01-15", 1], [2, "text"]]
384
+
385
+
386
+ def test_params_ensure_serialisable_custom_object():
387
+ """Test ensure_serialisable with custom object converts to string"""
388
+
389
+ class CustomClass:
390
+ def __str__(self):
391
+ return "custom_object"
392
+
393
+ obj = CustomClass()
394
+ result = Params.ensure_serialisable(obj)
395
+ assert result == "custom_object"
396
+ assert isinstance(result, str)
397
+
398
+
399
+ def test_params_ensure_serialisable_complex_nested():
400
+ """Test ensure_serialisable with complex nested structure"""
401
+ dt = datetime(2024, 1, 15)
402
+ nested_params = Params(inner_key="inner_value")
403
+ data = {"date": dt, "params": nested_params, "list": [dt, nested_params, 42], "nested": {"deep": {"date": dt}}}
404
+ result = Params.ensure_serialisable(data)
405
+ assert result["date"] == "2024-01-15"
406
+ assert result["params"]["inner_key"] == "inner_value"
407
+ assert result["list"][0] == "2024-01-15"
408
+ assert result["list"][1]["inner_key"] == "inner_value"
409
+ assert result["list"][2] == 42
410
+ assert result["nested"]["deep"]["date"] == "2024-01-15"
411
+
412
+
413
+ # ============================================================================
414
+ # Describe Method Tests
415
+ # ============================================================================
416
+
417
+
418
+ def test_params_describe_empty():
419
+ """Test describe with empty Params"""
420
+ params = Params()
421
+ assert params.describe() == {}
422
+
423
+
424
+ def test_params_describe_basic_types():
425
+ """Test describe with basic types"""
426
+ params = Params(string="text", number=42, boolean=True)
427
+ result = params.describe()
428
+ assert result == {"string": "text", "number": 42, "boolean": True}
429
+
430
+
431
+ def test_params_describe_with_datetime():
432
+ """Test describe serializes datetime to string"""
433
+ dt = datetime(2024, 1, 15, 10, 30, 0)
434
+ params = Params(date=dt, value=100)
435
+ result = params.describe()
436
+ assert result["date"] == "2024-01-15"
437
+ assert result["value"] == 100
438
+
439
+
440
+ def test_params_describe_with_nested_params():
441
+ """Test describe serializes nested Params"""
442
+ nested = Params(inner="value")
443
+ params = Params(outer="test", nested=nested)
444
+ result = params.describe()
445
+ assert result["outer"] == "test"
446
+ assert result["nested"] == {"inner": "value"}
447
+
448
+
449
+ def test_params_describe_excludes_private():
450
+ """Test describe excludes private attributes (starting with _)"""
451
+ params = Params(public="visible")
452
+ params._private = "hidden"
453
+ result = params.describe()
454
+ assert "public" in result
455
+ assert "_private" not in result
456
+
457
+
458
+ def test_params_describe_with_list():
459
+ """Test describe with list containing various types"""
460
+ dt = datetime(2024, 1, 15)
461
+ params = Params(items=[1, "text", dt])
462
+ result = params.describe()
463
+ assert result["items"] == [1, "text", "2024-01-15"]
464
+
465
+
466
+ def test_params_describe_with_dict():
467
+ """Test describe with dict containing datetime"""
468
+ dt = datetime(2024, 1, 15)
469
+ params = Params(data={"date": dt, "value": 100})
470
+ result = params.describe()
471
+ assert result["data"]["date"] == "2024-01-15"
472
+ assert result["data"]["value"] == 100
473
+
474
+
475
+ # ============================================================================
476
+ # To Dict Method Tests
477
+ # ============================================================================
478
+
479
+
480
+ def test_params_to_dict_empty():
481
+ """Test to_dict with empty Params"""
482
+ params = Params()
483
+ assert params.to_dict() == {}
484
+
485
+
486
+ def test_params_to_dict_basic():
487
+ """Test to_dict with basic types"""
488
+ params = Params(key="value", number=42)
489
+ result = params.to_dict()
490
+ assert result == {"key": "value", "number": 42}
491
+
492
+
493
+ def test_params_to_dict_preserves_datetime():
494
+ """Test to_dict preserves datetime objects (does not serialize)"""
495
+ dt = datetime(2024, 1, 15)
496
+ params = Params(date=dt)
497
+ result = params.to_dict()
498
+ assert result["date"] == dt
499
+ assert isinstance(result["date"], datetime)
500
+
501
+
502
+ def test_params_to_dict_preserves_params():
503
+ """Test to_dict preserves Params objects (does not serialize)"""
504
+ nested = Params(inner="value")
505
+ params = Params(nested=nested)
506
+ result = params.to_dict()
507
+ assert isinstance(result["nested"], Params)
508
+ assert result["nested"].inner == "value"
509
+
510
+
511
+ def test_params_to_dict_excludes_private():
512
+ """Test to_dict excludes private attributes"""
513
+ params = Params(public="visible")
514
+ params._private = "hidden"
515
+ result = params.to_dict()
516
+ assert "public" in result
517
+ assert "_private" not in result
518
+
519
+
520
+ def test_params_to_dict_vs_describe():
521
+ """Test differences between to_dict and describe"""
522
+ dt = datetime(2024, 1, 15)
523
+ nested = Params(inner="value")
524
+ params = Params(date=dt, nested=nested, value=100)
525
+
526
+ to_dict_result = params.to_dict()
527
+ describe_result = params.describe()
528
+
529
+ # to_dict preserves datetime
530
+ assert isinstance(to_dict_result["date"], datetime)
531
+ # describe serializes datetime
532
+ assert describe_result["date"] == "2024-01-15"
533
+
534
+ # to_dict preserves Params
535
+ assert isinstance(to_dict_result["nested"], Params)
536
+ # describe serializes Params
537
+ assert describe_result["nested"] == {"inner": "value"}
538
+
539
+
540
+ # ============================================================================
541
+ # Integration Tests
542
+ # ============================================================================
543
+
544
+
545
+ def test_params_full_workflow():
546
+ """Test complete workflow with Params"""
547
+ # Create from dict
548
+ data = {"symbol": "AAPL", "quantity": 100}
549
+ params = Params.from_dict(data)
550
+
551
+ # Add attributes
552
+ params.set("price", 150.5)
553
+ params.timestamp = datetime(2024, 1, 15)
554
+
555
+ # Check attributes
556
+ assert "symbol" in params
557
+ assert params.get("quantity") == 100
558
+
559
+ # Serialize
560
+ serialized = params.describe()
561
+ assert serialized["timestamp"] == "2024-01-15"
562
+
563
+
564
+ def test_params_dict_roundtrip():
565
+ """Test converting to dict and back"""
566
+ original = Params(key="value", number=42, flag=True)
567
+ data = original.to_dict()
568
+ restored = Params.from_dict(data)
569
+
570
+ assert restored.key == original.key
571
+ assert restored.number == original.number
572
+ assert restored.flag == original.flag
573
+
574
+
575
+ def test_params_api_request_simulation():
576
+ """Test simulating API request parameters"""
577
+ params = Params(symbol="AAPL", start_date=datetime(2024, 1, 1), end_date=datetime(2024, 1, 31), limit=100, offset=0)
578
+
579
+ # Serialize for API
580
+ api_params = params.describe()
581
+ assert api_params["symbol"] == "AAPL"
582
+ assert api_params["start_date"] == "2024-01-01"
583
+ assert api_params["end_date"] == "2024-01-31"
584
+ assert api_params["limit"] == 100
585
+
586
+
587
+ def test_params_nested_structure():
588
+ """Test complex nested structure"""
589
+ params = Params(
590
+ user=Params(name="John", id=123), preferences=Params(theme="dark", notifications=True), data=[1, 2, 3]
591
+ )
592
+
593
+ assert params.user.name == "John"
594
+ assert params.preferences.theme == "dark"
595
+
596
+ # Serialize entire structure
597
+ serialized = params.describe()
598
+ assert serialized["user"]["name"] == "John"
599
+ assert serialized["preferences"]["notifications"] is True
600
+
601
+
602
+ def test_params_modification_tracking():
603
+ """Test modifying params and tracking changes"""
604
+ params = Params(value=10)
605
+ original = params.to_dict()
606
+
607
+ params.set("value", 20)
608
+ params.set("new_field", "added")
609
+
610
+ modified = params.to_dict()
611
+ assert original != modified
612
+ assert modified["value"] == 20
613
+ assert "new_field" in modified
614
+
615
+
616
+ def test_params_multiple_instances_independence():
617
+ """Test multiple Params instances are independent"""
618
+ p1 = Params(value=10)
619
+ p2 = Params(value=20)
620
+
621
+ p1.set("value", 15)
622
+
623
+ assert p1.value == 15
624
+ assert p2.value == 20
625
+
626
+
627
+ def test_params_dynamic_attributes():
628
+ """Test dynamically adding and removing attributes"""
629
+ params = Params()
630
+
631
+ # Add attributes
632
+ for i in range(5):
633
+ params.set(f"key{i}", i * 10)
634
+
635
+ # Verify all added
636
+ for i in range(5):
637
+ assert f"key{i}" in params
638
+ assert params.get(f"key{i}") == i * 10
639
+
640
+
641
+ def test_params_empty_string_handling():
642
+ """Test handling of empty strings"""
643
+ params = Params(empty="", value="text")
644
+ assert params.empty == ""
645
+ assert params.value == "text"
646
+
647
+ result = params.to_dict()
648
+ assert result["empty"] == ""
649
+
650
+
651
+ def test_params_zero_value_handling():
652
+ """Test handling of zero values"""
653
+ params = Params(zero=0, positive=1, negative=-1)
654
+ assert params.zero == 0
655
+
656
+ result = params.describe()
657
+ assert result["zero"] == 0
658
+
659
+
660
+ def test_params_special_characters_in_keys():
661
+ """Test keys with special characters"""
662
+ params = Params()
663
+ params.set("key-with-dash", "value1")
664
+ params.set("key_with_underscore", "value2")
665
+ params.set("key.with.dot", "value3")
666
+
667
+ assert params.get("key-with-dash") == "value1"
668
+ assert params.get("key_with_underscore") == "value2"
669
+ assert params.get("key.with.dot") == "value3"
670
+
671
+
672
+ # ============================================================================
673
+ # Identifier Property Tests
674
+ # ============================================================================
675
+
676
+
677
+ def test_params_identifier_basic():
678
+ """Test identifier property returns SHA256 hash"""
679
+ params = Params(symbol="AAPL", quantity=100)
680
+ identifier = params.identifier
681
+
682
+ # SHA256 hash should be 64 characters (256 bits in hex)
683
+ assert isinstance(identifier, str)
684
+ assert len(identifier) == 64
685
+ assert all(c in "0123456789abcdef" for c in identifier)
686
+
687
+
688
+ def test_params_identifier_deterministic():
689
+ """Test that same params produce same identifier"""
690
+ params1 = Params(symbol="AAPL", price=150.5, quantity=100)
691
+ params2 = Params(symbol="AAPL", price=150.5, quantity=100)
692
+
693
+ assert params1.identifier == params2.identifier
694
+
695
+
696
+ def test_params_identifier_order_independent():
697
+ """Test that parameter order doesn't affect identifier"""
698
+ # Different order of initialization
699
+ params1 = Params(symbol="AAPL", price=150.5, quantity=100)
700
+ params2 = Params(quantity=100, symbol="AAPL", price=150.5)
701
+
702
+ # Should produce same identifier because JSON uses sorted keys
703
+ assert params1.identifier == params2.identifier
704
+
705
+
706
+ def test_params_identifier_different_values():
707
+ """Test that different values produce different identifiers"""
708
+ params1 = Params(symbol="AAPL", quantity=100)
709
+ params2 = Params(symbol="AAPL", quantity=200)
710
+
711
+ assert params1.identifier != params2.identifier
712
+
713
+
714
+ def test_params_identifier_different_keys():
715
+ """Test that different keys produce different identifiers"""
716
+ params1 = Params(symbol="AAPL", quantity=100)
717
+ params2 = Params(symbol="AAPL", amount=100)
718
+
719
+ assert params1.identifier != params2.identifier
720
+
721
+
722
+ def test_params_identifier_empty_params():
723
+ """Test identifier for empty Params"""
724
+ params = Params()
725
+ identifier = params.identifier
726
+
727
+ assert isinstance(identifier, str)
728
+ assert len(identifier) == 64
729
+
730
+
731
+ def test_params_identifier_with_datetime():
732
+ """Test identifier handles datetime serialization"""
733
+ dt = datetime(2024, 1, 15, 10, 30, 0)
734
+ params1 = Params(timestamp=dt)
735
+ params2 = Params(timestamp=dt)
736
+
737
+ # Same datetime should produce same identifier
738
+ assert params1.identifier == params2.identifier
739
+
740
+ # Different datetime should produce different identifier
741
+ params3 = Params(timestamp=datetime(2024, 1, 16, 10, 30, 0))
742
+ assert params1.identifier != params3.identifier
743
+
744
+
745
+ def test_params_identifier_with_nested_params():
746
+ """Test identifier with nested Params objects"""
747
+ nested1 = Params(inner="value1")
748
+ nested2 = Params(inner="value1")
749
+
750
+ params1 = Params(outer="test", nested=nested1)
751
+ params2 = Params(outer="test", nested=nested2)
752
+
753
+ # Same nested structure should produce same identifier
754
+ assert params1.identifier == params2.identifier
755
+
756
+
757
+ def test_params_identifier_with_nested_dict():
758
+ """Test identifier with nested dictionary"""
759
+ params1 = Params(data={"key1": "value1", "key2": "value2"})
760
+ params2 = Params(data={"key2": "value2", "key1": "value1"})
761
+
762
+ # Order in nested dict shouldn't matter
763
+ assert params1.identifier == params2.identifier
764
+
765
+
766
+ def test_params_identifier_with_list():
767
+ """Test identifier with list values"""
768
+ params1 = Params(items=[1, 2, 3])
769
+ params2 = Params(items=[1, 2, 3])
770
+ params3 = Params(items=[3, 2, 1])
771
+
772
+ # Same list should produce same identifier
773
+ assert params1.identifier == params2.identifier
774
+
775
+ # Different order in list should produce different identifier
776
+ assert params1.identifier != params3.identifier
777
+
778
+
779
+ def test_params_identifier_with_various_types():
780
+ """Test identifier with mixed data types"""
781
+ params = Params(
782
+ string="text",
783
+ integer=42,
784
+ floating=3.14,
785
+ boolean=True,
786
+ none_value=None,
787
+ list_value=[1, 2, 3],
788
+ dict_value={"key": "value"},
789
+ )
790
+
791
+ identifier = params.identifier
792
+ assert isinstance(identifier, str)
793
+ assert len(identifier) == 64
794
+
795
+
796
+ def test_params_identifier_with_unicode():
797
+ """Test identifier handles Unicode characters"""
798
+ params1 = Params(name="中文测试", symbol="股票")
799
+ params2 = Params(name="中文测试", symbol="股票")
800
+
801
+ assert params1.identifier == params2.identifier
802
+
803
+
804
+ def test_params_identifier_with_special_characters():
805
+ """Test identifier with special characters in values"""
806
+ params1 = Params(text="Hello, World! @#$%^&*()")
807
+ params2 = Params(text="Hello, World! @#$%^&*()")
808
+
809
+ assert params1.identifier == params2.identifier
810
+
811
+
812
+ def test_params_identifier_immutability():
813
+ """Test that identifier changes when params are modified"""
814
+ params = Params(symbol="AAPL", quantity=100)
815
+ identifier1 = params.identifier
816
+
817
+ # Modify params
818
+ params.quantity = 200
819
+ identifier2 = params.identifier
820
+
821
+ # Identifier should change
822
+ assert identifier1 != identifier2
823
+
824
+
825
+ def test_params_identifier_with_complex_nested_structure():
826
+ """Test identifier with deeply nested structures"""
827
+ params1 = Params(
828
+ level1={"level2": {"level3": {"value": "deep"}}},
829
+ list_of_dicts=[{"a": 1}, {"b": 2}],
830
+ )
831
+ params2 = Params(
832
+ level1={"level2": {"level3": {"value": "deep"}}},
833
+ list_of_dicts=[{"a": 1}, {"b": 2}],
834
+ )
835
+
836
+ assert params1.identifier == params2.identifier
837
+
838
+
839
+ def test_params_identifier_consistency_after_modification():
840
+ """Test that adding new attributes changes identifier"""
841
+ params = Params(symbol="AAPL")
842
+ identifier1 = params.identifier
843
+
844
+ # Add new attribute
845
+ params.quantity = 100
846
+ identifier2 = params.identifier
847
+
848
+ assert identifier1 != identifier2
849
+
850
+ # Remove attribute
851
+ delattr(params, "quantity")
852
+ identifier3 = params.identifier
853
+
854
+ # Should be back to original identifier
855
+ assert identifier1 == identifier3
856
+
857
+
858
+ def test_params_identifier_with_datetime_serialization():
859
+ """Test that datetime is properly serialized in identifier"""
860
+ dt = datetime(2024, 1, 15)
861
+ params1 = Params(date=dt)
862
+ # Manually create params with string date (what describe() converts to)
863
+ params2 = Params(date="2024-01-15")
864
+
865
+ # These should produce same identifier since datetime is serialized to string
866
+ assert params1.identifier == params2.identifier
867
+
868
+
869
+ def test_params_identifier_hash_distribution():
870
+ """Test that similar params produce different hashes (no collision)"""
871
+ identifiers = set()
872
+
873
+ for i in range(100):
874
+ params = Params(index=i, value=f"test_{i}")
875
+ identifiers.add(params.identifier)
876
+
877
+ # All 100 should be unique
878
+ assert len(identifiers) == 100
879
+
880
+
881
+ def test_params_identifier_stability():
882
+ """Test identifier remains stable across multiple reads"""
883
+ params = Params(symbol="AAPL", quantity=100, price=150.5)
884
+
885
+ # Read identifier multiple times
886
+ id1 = params.identifier
887
+ id2 = params.identifier
888
+ id3 = params.identifier
889
+
890
+ # All should be identical
891
+ assert id1 == id2 == id3