graphsense-python 2.15.0__tar.gz → 2.15.2__tar.gz

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 (231) hide show
  1. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/PKG-INFO +3 -3
  2. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/README.md +2 -2
  3. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/__init__.py +1 -1
  4. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/addresses_api.py +3 -3
  5. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/bulk_api.py +12 -6
  6. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/clusters_api.py +3 -3
  7. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/entities_api.py +3 -3
  8. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api_client.py +1 -1
  9. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/configuration.py +2 -2
  10. graphsense_python-2.15.2/graphsense/ext/bulk.py +116 -0
  11. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/ext/client.py +20 -3
  12. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address.py +2 -2
  13. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/entity.py +1 -1
  14. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/neighbor_address.py +3 -3
  15. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/neighbor_cluster.py +3 -3
  16. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/neighbor_entity.py +3 -3
  17. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense_python.egg-info/PKG-INFO +3 -3
  18. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/pyproject.toml +1 -1
  19. graphsense_python-2.15.2/tests/test_ext_bulk.py +105 -0
  20. graphsense_python-2.15.0/graphsense/ext/bulk.py +0 -51
  21. graphsense_python-2.15.0/tests/test_ext_bulk.py +0 -53
  22. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/address_scan/__init__.py +0 -0
  23. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/address_scan/decompress.py +0 -0
  24. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/address_scan/detectors.py +0 -0
  25. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/address_scan/scanner.py +0 -0
  26. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/address_scan/validators.py +0 -0
  27. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/__init__.py +0 -0
  28. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/blocks_api.py +0 -0
  29. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/general_api.py +0 -0
  30. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/graph_api.py +0 -0
  31. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/rates_api.py +0 -0
  32. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/tags_api.py +0 -0
  33. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/tokens_api.py +0 -0
  34. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api/txs_api.py +0 -0
  35. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/api_response.py +0 -0
  36. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/__init__.py +0 -0
  37. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/__main__.py +0 -0
  38. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/bulk_cmd.py +0 -0
  39. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/context.py +0 -0
  40. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/convenience.py +0 -0
  41. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/errors.py +0 -0
  42. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/gs.py +0 -0
  43. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/main.py +0 -0
  44. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/raw.py +0 -0
  45. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/cli/scan.py +0 -0
  46. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/compat.py +0 -0
  47. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/exceptions.py +0 -0
  48. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/ext/__init__.py +0 -0
  49. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/ext/deprecation.py +0 -0
  50. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/ext/io.py +0 -0
  51. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/ext/output.py +0 -0
  52. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/ext/selectors.py +0 -0
  53. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/gs_files/__init__.py +0 -0
  54. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/gs_files/encoder.py +0 -0
  55. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/gs_files/parser.py +0 -0
  56. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/gs_files/summary.py +0 -0
  57. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/gs_files/writer.py +0 -0
  58. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/__init__.py +0 -0
  59. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/actor.py +0 -0
  60. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/actor_context.py +0 -0
  61. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/address.py +0 -0
  62. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/address_tag.py +0 -0
  63. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/address_tags.py +0 -0
  64. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/address_tx.py +0 -0
  65. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/address_tx_utxo.py +0 -0
  66. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/address_txs.py +0 -0
  67. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/block.py +0 -0
  68. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/block_at_date.py +0 -0
  69. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/concept.py +0 -0
  70. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/currency_stats.py +0 -0
  71. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/entity.py +0 -0
  72. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/entity_addresses.py +0 -0
  73. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/external_conversion.py +0 -0
  74. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/http_validation_error.py +0 -0
  75. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/label_summary.py +0 -0
  76. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/labeled_item_ref.py +0 -0
  77. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/link.py +0 -0
  78. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/link_utxo.py +0 -0
  79. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/links.py +0 -0
  80. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/links_inner.py +0 -0
  81. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/location_inner.py +0 -0
  82. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/neighbor_address.py +0 -0
  83. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/neighbor_addresses.py +0 -0
  84. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/neighbor_entities.py +0 -0
  85. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/neighbor_entity.py +0 -0
  86. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/rate.py +0 -0
  87. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/rates.py +0 -0
  88. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/related_address.py +0 -0
  89. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/related_addresses.py +0 -0
  90. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result.py +0 -0
  91. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_by_currency.py +0 -0
  92. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_level1.py +0 -0
  93. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_level2.py +0 -0
  94. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_level3.py +0 -0
  95. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_level4.py +0 -0
  96. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_level5.py +0 -0
  97. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/search_result_level6.py +0 -0
  98. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/stats.py +0 -0
  99. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tag.py +0 -0
  100. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tag_cloud_entry.py +0 -0
  101. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tag_summary.py +0 -0
  102. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/taxonomy.py +0 -0
  103. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/token_config.py +0 -0
  104. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/token_configs.py +0 -0
  105. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tx.py +0 -0
  106. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tx_account.py +0 -0
  107. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tx_ref.py +0 -0
  108. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tx_summary.py +0 -0
  109. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tx_utxo.py +0 -0
  110. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/tx_value.py +0 -0
  111. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/user_reported_tag.py +0 -0
  112. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/user_tag_report_response.py +0 -0
  113. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/validation_error.py +0 -0
  114. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/model/values.py +0 -0
  115. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/__init__.py +0 -0
  116. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/actor.py +0 -0
  117. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/actor_context.py +0 -0
  118. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address_output.py +0 -0
  119. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address_tag.py +0 -0
  120. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address_tags.py +0 -0
  121. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address_tx.py +0 -0
  122. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address_tx_utxo.py +0 -0
  123. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/address_txs.py +0 -0
  124. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/block.py +0 -0
  125. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/block_at_date.py +0 -0
  126. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/change_heuristics.py +0 -0
  127. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/cluster.py +0 -0
  128. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/cluster_addresses.py +0 -0
  129. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/coin_join_consensus.py +0 -0
  130. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/coin_join_heuristics.py +0 -0
  131. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/comparison_signal.py +0 -0
  132. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/comparison_verdict.py +0 -0
  133. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/concept.py +0 -0
  134. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/consensus_entry.py +0 -0
  135. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/currency_stats.py +0 -0
  136. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/direct_change_heuristic.py +0 -0
  137. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/entity_addresses.py +0 -0
  138. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/external_conversion.py +0 -0
  139. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_address_network_summary.py +0 -0
  140. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_address_overall.py +0 -0
  141. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_address_ref.py +0 -0
  142. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_address_summary.py +0 -0
  143. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_compare_note.py +0 -0
  144. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_compare_request.py +0 -0
  145. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_compare_signal.py +0 -0
  146. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_compare_verdict.py +0 -0
  147. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_compared_tx.py +0 -0
  148. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_comparison.py +0 -0
  149. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_lineage_edge.py +0 -0
  150. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_note.py +0 -0
  151. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_summary.py +0 -0
  152. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_summary_request.py +0 -0
  153. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_tx_characteristics.py +0 -0
  154. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_tx_network_summary.py +0 -0
  155. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_tx_overall.py +0 -0
  156. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_tx_ref.py +0 -0
  157. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/graph_tx_summary.py +0 -0
  158. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/http_validation_error.py +0 -0
  159. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/join_market_heuristic.py +0 -0
  160. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/label_summary.py +0 -0
  161. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/labeled_item_ref.py +0 -0
  162. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/lineage_edge.py +0 -0
  163. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/link.py +0 -0
  164. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/link_utxo.py +0 -0
  165. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/links.py +0 -0
  166. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/links_inner.py +0 -0
  167. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/location_inner.py +0 -0
  168. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/multi_input_change_heuristic.py +0 -0
  169. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/neighbor_addresses.py +0 -0
  170. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/neighbor_clusters.py +0 -0
  171. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/neighbor_entities.py +0 -0
  172. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/one_time_change_heuristic.py +0 -0
  173. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/per_tx_inner.py +0 -0
  174. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/rate.py +0 -0
  175. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/rates.py +0 -0
  176. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/related_address.py +0 -0
  177. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/related_addresses.py +0 -0
  178. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result.py +0 -0
  179. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_by_currency.py +0 -0
  180. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_level1.py +0 -0
  181. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_level2.py +0 -0
  182. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_level3.py +0 -0
  183. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_level4.py +0 -0
  184. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_level5.py +0 -0
  185. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/search_result_level6.py +0 -0
  186. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/stats.py +0 -0
  187. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tag.py +0 -0
  188. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tag_cloud_entry.py +0 -0
  189. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tag_summary.py +0 -0
  190. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/taxonomy.py +0 -0
  191. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/token_config.py +0 -0
  192. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/token_configs.py +0 -0
  193. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/transaction_comparison.py +0 -0
  194. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx.py +0 -0
  195. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_account.py +0 -0
  196. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_characteristics.py +0 -0
  197. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_compared_item.py +0 -0
  198. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_ref.py +0 -0
  199. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_summary.py +0 -0
  200. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_utxo.py +0 -0
  201. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/tx_value.py +0 -0
  202. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/user_reported_tag.py +0 -0
  203. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/user_tag_report_response.py +0 -0
  204. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/utxo_heuristics.py +0 -0
  205. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/validation_error.py +0 -0
  206. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/values.py +0 -0
  207. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/wasabi_heuristic.py +0 -0
  208. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/whirlpool_coin_join_heuristic.py +0 -0
  209. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/models/whirlpool_tx0_heuristic.py +0 -0
  210. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/py.typed +0 -0
  211. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense/rest.py +0 -0
  212. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense_python.egg-info/SOURCES.txt +0 -0
  213. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense_python.egg-info/dependency_links.txt +0 -0
  214. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense_python.egg-info/entry_points.txt +0 -0
  215. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense_python.egg-info/requires.txt +0 -0
  216. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/graphsense_python.egg-info/top_level.txt +0 -0
  217. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/setup.cfg +0 -0
  218. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_bulk.py +0 -0
  219. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_convenience.py +0 -0
  220. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_errors.py +0 -0
  221. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_gs.py +0 -0
  222. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_io_pipes.py +0 -0
  223. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_raw_mirror.py +0 -0
  224. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_scan.py +0 -0
  225. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_cli_tags_and_dates.py +0 -0
  226. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_ext_client.py +0 -0
  227. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_ext_io.py +0 -0
  228. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_ext_output.py +0 -0
  229. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_ext_selectors.py +0 -0
  230. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_readme_template.py +0 -0
  231. {graphsense_python-2.15.0 → graphsense_python-2.15.2}/tests/test_regen_survives.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphsense-python
3
- Version: 2.15.0
3
+ Version: 2.15.2
4
4
  Summary: GraphSense API
5
5
  Author-email: Iknaio Cryptoasset Analytics GmbH <contact@iknaio.com>
6
6
  License-Expression: MIT
@@ -63,8 +63,8 @@ connector or tool source; the assistant then handles the rest.
63
63
 
64
64
  This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
65
65
 
66
- - API version: 2.15.0
67
- - Package version: 2.15.0
66
+ - API version: 2.15.2
67
+ - Package version: 2.15.2
68
68
  - Generator version: 7.19.0
69
69
  - Build package: org.openapitools.codegen.languages.PythonClientCodegen
70
70
  For more information, please visit [https://www.iknaio.com/](https://www.iknaio.com/)
@@ -41,8 +41,8 @@ connector or tool source; the assistant then handles the rest.
41
41
 
42
42
  This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
43
43
 
44
- - API version: 2.15.0
45
- - Package version: 2.15.0
44
+ - API version: 2.15.2
45
+ - Package version: 2.15.2
46
46
  - Generator version: 7.19.0
47
47
  - Build package: org.openapitools.codegen.languages.PythonClientCodegen
48
48
  For more information, please visit [https://www.iknaio.com/](https://www.iknaio.com/)
@@ -12,7 +12,7 @@
12
12
  """
13
13
 
14
14
 
15
- __version__ = "2.15.0"
15
+ __version__ = "2.15.2"
16
16
 
17
17
  # Define package exports
18
18
  __all__ = [
@@ -1795,7 +1795,7 @@ class AddressesApi:
1795
1795
  ) -> NeighborAddresses:
1796
1796
  """List neighboring addresses
1797
1797
 
1798
- Returns neighboring addresses connected to the given address in the transaction graph.
1798
+ Returns neighboring addresses connected to the given address in the transaction graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried address and that neighbor), while the embedded `address` object carries the neighbor's own **lifetime** attributes (its balance, degrees, first_tx/last_tx, …), not relative to the queried address.
1799
1799
 
1800
1800
  :param currency: The cryptocurrency code (e.g., btc) (required)
1801
1801
  :type currency: str
@@ -1892,7 +1892,7 @@ class AddressesApi:
1892
1892
  ) -> ApiResponse[NeighborAddresses]:
1893
1893
  """List neighboring addresses
1894
1894
 
1895
- Returns neighboring addresses connected to the given address in the transaction graph.
1895
+ Returns neighboring addresses connected to the given address in the transaction graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried address and that neighbor), while the embedded `address` object carries the neighbor's own **lifetime** attributes (its balance, degrees, first_tx/last_tx, …), not relative to the queried address.
1896
1896
 
1897
1897
  :param currency: The cryptocurrency code (e.g., btc) (required)
1898
1898
  :type currency: str
@@ -1989,7 +1989,7 @@ class AddressesApi:
1989
1989
  ) -> RESTResponseType:
1990
1990
  """List neighboring addresses
1991
1991
 
1992
- Returns neighboring addresses connected to the given address in the transaction graph.
1992
+ Returns neighboring addresses connected to the given address in the transaction graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried address and that neighbor), while the embedded `address` object carries the neighbor's own **lifetime** attributes (its balance, degrees, first_tx/last_tx, …), not relative to the queried address.
1993
1993
 
1994
1994
  :param currency: The cryptocurrency code (e.g., btc) (required)
1995
1995
  :type currency: str
@@ -147,7 +147,7 @@ class BulkApi:
147
147
  ) -> object:
148
148
  """Stream bulk operation results as CSV
149
149
 
150
- Executes a supported operation for multiple key values and streams flattened result rows as CSV.
150
+ Executes a supported operation for multiple key values and streams flattened result rows as CSV. Each key list in the request body is capped (10,000 items by default), as is the body itself (8 MiB by default); split longer requests into several calls.
151
151
 
152
152
  :param currency: The cryptocurrency code (e.g., btc) (required)
153
153
  :type currency: str
@@ -193,6 +193,7 @@ class BulkApi:
193
193
  _response_types_map: Dict[str, Optional[str]] = {
194
194
  '200': "object",
195
195
  '400': None,
196
+ '413': None,
196
197
  '422': None,
197
198
  }
198
199
  response_data = self.api_client.call_api(
@@ -228,7 +229,7 @@ class BulkApi:
228
229
  ) -> ApiResponse[object]:
229
230
  """Stream bulk operation results as CSV
230
231
 
231
- Executes a supported operation for multiple key values and streams flattened result rows as CSV.
232
+ Executes a supported operation for multiple key values and streams flattened result rows as CSV. Each key list in the request body is capped (10,000 items by default), as is the body itself (8 MiB by default); split longer requests into several calls.
232
233
 
233
234
  :param currency: The cryptocurrency code (e.g., btc) (required)
234
235
  :type currency: str
@@ -274,6 +275,7 @@ class BulkApi:
274
275
  _response_types_map: Dict[str, Optional[str]] = {
275
276
  '200': "object",
276
277
  '400': None,
278
+ '413': None,
277
279
  '422': None,
278
280
  }
279
281
  response_data = self.api_client.call_api(
@@ -309,7 +311,7 @@ class BulkApi:
309
311
  ) -> RESTResponseType:
310
312
  """Stream bulk operation results as CSV
311
313
 
312
- Executes a supported operation for multiple key values and streams flattened result rows as CSV.
314
+ Executes a supported operation for multiple key values and streams flattened result rows as CSV. Each key list in the request body is capped (10,000 items by default), as is the body itself (8 MiB by default); split longer requests into several calls.
313
315
 
314
316
  :param currency: The cryptocurrency code (e.g., btc) (required)
315
317
  :type currency: str
@@ -355,6 +357,7 @@ class BulkApi:
355
357
  _response_types_map: Dict[str, Optional[str]] = {
356
358
  '200': "object",
357
359
  '400': None,
360
+ '413': None,
358
361
  '422': None,
359
362
  }
360
363
  response_data = self.api_client.call_api(
@@ -474,7 +477,7 @@ class BulkApi:
474
477
  ) -> object:
475
478
  """Stream bulk operation results as JSON
476
479
 
477
- Executes a supported operation for multiple key values and streams flattened result rows as JSON.
480
+ Executes a supported operation for multiple key values and streams flattened result rows as JSON. Each key list in the request body is capped (10,000 items by default), as is the body itself (8 MiB by default); split longer requests into several calls.
478
481
 
479
482
  :param currency: The cryptocurrency code (e.g., btc) (required)
480
483
  :type currency: str
@@ -520,6 +523,7 @@ class BulkApi:
520
523
  _response_types_map: Dict[str, Optional[str]] = {
521
524
  '200': "object",
522
525
  '400': None,
526
+ '413': None,
523
527
  '422': None,
524
528
  }
525
529
  response_data = self.api_client.call_api(
@@ -555,7 +559,7 @@ class BulkApi:
555
559
  ) -> ApiResponse[object]:
556
560
  """Stream bulk operation results as JSON
557
561
 
558
- Executes a supported operation for multiple key values and streams flattened result rows as JSON.
562
+ Executes a supported operation for multiple key values and streams flattened result rows as JSON. Each key list in the request body is capped (10,000 items by default), as is the body itself (8 MiB by default); split longer requests into several calls.
559
563
 
560
564
  :param currency: The cryptocurrency code (e.g., btc) (required)
561
565
  :type currency: str
@@ -601,6 +605,7 @@ class BulkApi:
601
605
  _response_types_map: Dict[str, Optional[str]] = {
602
606
  '200': "object",
603
607
  '400': None,
608
+ '413': None,
604
609
  '422': None,
605
610
  }
606
611
  response_data = self.api_client.call_api(
@@ -636,7 +641,7 @@ class BulkApi:
636
641
  ) -> RESTResponseType:
637
642
  """Stream bulk operation results as JSON
638
643
 
639
- Executes a supported operation for multiple key values and streams flattened result rows as JSON.
644
+ Executes a supported operation for multiple key values and streams flattened result rows as JSON. Each key list in the request body is capped (10,000 items by default), as is the body itself (8 MiB by default); split longer requests into several calls.
640
645
 
641
646
  :param currency: The cryptocurrency code (e.g., btc) (required)
642
647
  :type currency: str
@@ -682,6 +687,7 @@ class BulkApi:
682
687
  _response_types_map: Dict[str, Optional[str]] = {
683
688
  '200': "object",
684
689
  '400': None,
690
+ '413': None,
685
691
  '422': None,
686
692
  }
687
693
  response_data = self.api_client.call_api(
@@ -1549,7 +1549,7 @@ class ClustersApi:
1549
1549
  ) -> NeighborClusters:
1550
1550
  """(Deprecated) List neighboring clusters
1551
1551
 
1552
- Returns neighboring clusters connected to the given cluster in the cluster graph.
1552
+ Returns neighboring clusters connected to the given cluster in the cluster graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried cluster and that neighbor), while the embedded cluster object carries the neighbor's own **lifetime** attributes, not relative to the queried cluster.
1553
1553
 
1554
1554
  :param currency: The cryptocurrency code (e.g., btc) (required)
1555
1555
  :type currency: str
@@ -1655,7 +1655,7 @@ class ClustersApi:
1655
1655
  ) -> ApiResponse[NeighborClusters]:
1656
1656
  """(Deprecated) List neighboring clusters
1657
1657
 
1658
- Returns neighboring clusters connected to the given cluster in the cluster graph.
1658
+ Returns neighboring clusters connected to the given cluster in the cluster graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried cluster and that neighbor), while the embedded cluster object carries the neighbor's own **lifetime** attributes, not relative to the queried cluster.
1659
1659
 
1660
1660
  :param currency: The cryptocurrency code (e.g., btc) (required)
1661
1661
  :type currency: str
@@ -1761,7 +1761,7 @@ class ClustersApi:
1761
1761
  ) -> RESTResponseType:
1762
1762
  """(Deprecated) List neighboring clusters
1763
1763
 
1764
- Returns neighboring clusters connected to the given cluster in the cluster graph.
1764
+ Returns neighboring clusters connected to the given cluster in the cluster graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried cluster and that neighbor), while the embedded cluster object carries the neighbor's own **lifetime** attributes, not relative to the queried cluster.
1765
1765
 
1766
1766
  :param currency: The cryptocurrency code (e.g., btc) (required)
1767
1767
  :type currency: str
@@ -1555,7 +1555,7 @@ class EntitiesApi:
1555
1555
  ) -> NeighborEntities:
1556
1556
  """(Deprecated) List neighboring entities
1557
1557
 
1558
- Returns neighboring entities connected to the given entity in the entity graph.
1558
+ Returns neighboring entities connected to the given entity in the entity graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried entity and that neighbor), while the embedded entity/cluster object carries the neighbor's own **lifetime** attributes, not relative to the queried entity.
1559
1559
 
1560
1560
  :param currency: The cryptocurrency code (e.g., btc) (required)
1561
1561
  :type currency: str
@@ -1661,7 +1661,7 @@ class EntitiesApi:
1661
1661
  ) -> ApiResponse[NeighborEntities]:
1662
1662
  """(Deprecated) List neighboring entities
1663
1663
 
1664
- Returns neighboring entities connected to the given entity in the entity graph.
1664
+ Returns neighboring entities connected to the given entity in the entity graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried entity and that neighbor), while the embedded entity/cluster object carries the neighbor's own **lifetime** attributes, not relative to the queried entity.
1665
1665
 
1666
1666
  :param currency: The cryptocurrency code (e.g., btc) (required)
1667
1667
  :type currency: str
@@ -1767,7 +1767,7 @@ class EntitiesApi:
1767
1767
  ) -> RESTResponseType:
1768
1768
  """(Deprecated) List neighboring entities
1769
1769
 
1770
- Returns neighboring entities connected to the given entity in the entity graph.
1770
+ Returns neighboring entities connected to the given entity in the entity graph. Per neighbor, `value` and `no_txs` are **edge-scoped** (the value transferred and number of transactions between the queried entity and that neighbor), while the embedded entity/cluster object carries the neighbor's own **lifetime** attributes, not relative to the queried entity.
1771
1771
 
1772
1772
  :param currency: The cryptocurrency code (e.g., btc) (required)
1773
1773
  :type currency: str
@@ -95,7 +95,7 @@ class ApiClient:
95
95
  self.default_headers[header_name] = header_value
96
96
  self.cookie = cookie
97
97
  # Set default User-Agent.
98
- self.user_agent = 'graphsense-python/2.15.0'
98
+ self.user_agent = 'graphsense-python/2.15.2'
99
99
  self.client_side_validation = configuration.client_side_validation
100
100
 
101
101
  def __enter__(self):
@@ -533,8 +533,8 @@ conf = graphsense.Configuration(
533
533
  return "Python SDK Debug Report:\n"\
534
534
  "OS: {env}\n"\
535
535
  "Python Version: {pyversion}\n"\
536
- "Version of the API: 2.15.0\n"\
537
- "SDK Package Version: 2.15.0".\
536
+ "Version of the API: 2.15.2\n"\
537
+ "SDK Package Version: 2.15.2".\
538
538
  format(env=sys.platform, pyversion=sys.version)
539
539
 
540
540
  def get_host_settings(self) -> List[HostSetting]:
@@ -0,0 +1,116 @@
1
+ """Decide between per-item calls and the /bulk endpoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import csv
6
+ import io
7
+ import sys
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from typing import Any, Callable, Iterable, Iterator, Optional
10
+
11
+ DEFAULT_BULK_THRESHOLD = 10
12
+
13
+ # The server bounds how many items one bulk request may carry (10,000 by
14
+ # default; see GHSA-372j-2wgf-23ch) and answers a longer list with a 400.
15
+ # Splitting below that bound keeps arbitrarily long key lists working from the
16
+ # client's side. Deployments that lower the server cap need a matching
17
+ # `chunk_size` on the call.
18
+ DEFAULT_BULK_CHUNK_SIZE = 5000
19
+
20
+
21
+ def should_bulk(
22
+ n: int,
23
+ *,
24
+ threshold: int = DEFAULT_BULK_THRESHOLD,
25
+ override: Optional[bool] = None,
26
+ ) -> bool:
27
+ """True if we should go through the bulk endpoint.
28
+
29
+ `override=None`: use threshold.
30
+ `override=True`: always bulk (even for small N).
31
+ `override=False`: never bulk.
32
+ """
33
+ if override is not None:
34
+ return override
35
+ return n >= threshold
36
+
37
+
38
+ def run_parallel(
39
+ call: Callable[[str], Any],
40
+ keys: Iterable[str],
41
+ *,
42
+ max_workers: int = 8,
43
+ ) -> list[Any]:
44
+ """Call `call(k)` for each k in keys across a thread pool, preserving order."""
45
+ items = list(keys)
46
+ with ThreadPoolExecutor(max_workers=max_workers) as pool:
47
+ return list(pool.map(call, items))
48
+
49
+
50
+ def announce_switch(stream=None) -> None:
51
+ """Emit a one-line stderr notice the first time we switch to bulk.
52
+
53
+ Caller must implement the "first time" logic — this just prints.
54
+ """
55
+ out = stream if stream is not None else sys.stderr
56
+ print(
57
+ "notice: switching to bulk endpoint "
58
+ "(rows are flat; per-item typed models are available with --no-bulk)",
59
+ file=out,
60
+ )
61
+
62
+
63
+ def chunked(items: Iterable[Any], size: int) -> Iterator[list]:
64
+ """Yield consecutive lists of at most `size` items."""
65
+ if size <= 0:
66
+ raise ValueError("chunk size must be positive")
67
+ batch: list = []
68
+ for item in items:
69
+ batch.append(item)
70
+ if len(batch) == size:
71
+ yield batch
72
+ batch = []
73
+ if batch:
74
+ yield batch
75
+
76
+
77
+ def merge_json_chunks(chunks: Iterable[Any]) -> list:
78
+ """Concatenate the row lists returned by several bulk.json calls."""
79
+ rows: list = []
80
+ for chunk in chunks:
81
+ if chunk is None:
82
+ continue
83
+ rows.extend(chunk if isinstance(chunk, list) else [chunk])
84
+ return rows
85
+
86
+
87
+ def merge_csv_chunks(chunks: Iterable[str]) -> str:
88
+ """Concatenate several bulk.csv responses into one CSV document.
89
+
90
+ Each response carries its own header, and the server infers those columns
91
+ from the first rows of that request — so two chunks of the same query can
92
+ legitimately disagree on the column set. Rows are therefore re-keyed onto
93
+ the union of all headers (in first-seen order) rather than pasted together.
94
+ """
95
+ rows: list[dict] = []
96
+ columns: list[str] = []
97
+ seen = set()
98
+ for chunk in chunks:
99
+ if not chunk or not chunk.strip():
100
+ continue
101
+ reader = csv.DictReader(io.StringIO(chunk))
102
+ for name in reader.fieldnames or []:
103
+ if name not in seen:
104
+ seen.add(name)
105
+ columns.append(name)
106
+ rows.extend(reader)
107
+
108
+ if not columns:
109
+ return ""
110
+
111
+ out = io.StringIO()
112
+ writer = csv.DictWriter(out, columns, restval="", extrasaction="ignore")
113
+ writer.writeheader()
114
+ for row in rows:
115
+ writer.writerow(row)
116
+ return out.getvalue()
@@ -26,6 +26,7 @@ from dateutil import parser as _dateutil_parser
26
26
  import graphsense
27
27
  from graphsense.api_client import ApiClient
28
28
  from graphsense.configuration import Configuration
29
+ from graphsense.ext import bulk as bulk_mod
29
30
  from graphsense.ext.deprecation import install as install_deprecation_hook
30
31
 
31
32
  # APIs that expose only deprecated endpoints; hidden from the convenience
@@ -506,15 +507,31 @@ class GraphSense:
506
507
  format: str = "json",
507
508
  num_pages: int = 1,
508
509
  key_field: str = "address",
510
+ chunk_size: int = bulk_mod.DEFAULT_BULK_CHUNK_SIZE,
509
511
  ) -> Any:
510
512
  """Call the /bulk.<format>/<operation> endpoint with `keys`.
511
513
 
512
514
  `key_field` is the dict key that the bulk operation expects in the
513
515
  request body (e.g. "address", "tx_hash", "cluster"). Default is
514
516
  "address" which is by far the most common.
517
+
518
+ The server bounds how many items a single bulk request may carry, so a
519
+ longer key list is split into `chunk_size` batches and the responses are
520
+ merged. Pass `chunk_size=0` to send the list in one request regardless.
515
521
  """
516
522
  ccy = self._currency(currency)
517
- body = {key_field: list(keys)}
523
+ keys = list(keys)
524
+
525
+ def call(batch: list) -> Any:
526
+ body = {key_field: batch}
527
+ if format == "csv":
528
+ return self.raw.bulk.bulk_csv(ccy, operation, num_pages, body)
529
+ return self.raw.bulk.bulk_json(ccy, operation, num_pages, body)
530
+
531
+ if chunk_size <= 0 or len(keys) <= chunk_size:
532
+ return call(keys)
533
+
534
+ results = [call(batch) for batch in bulk_mod.chunked(keys, chunk_size)]
518
535
  if format == "csv":
519
- return self.raw.bulk.bulk_csv(ccy, operation, num_pages, body)
520
- return self.raw.bulk.bulk_json(ccy, operation, num_pages, body)
536
+ return bulk_mod.merge_csv_chunks(results)
537
+ return bulk_mod.merge_json_chunks(results)
@@ -35,8 +35,8 @@ class Address(BaseModel):
35
35
  balance: Values
36
36
  total_received: Values
37
37
  total_spent: Values
38
- first_tx: Optional[TxSummary] = None
39
- last_tx: Optional[TxSummary] = None
38
+ first_tx: Optional[TxSummary] = Field(default=None, description="First transaction in which this address appears, over its entire history — independent of any neighbor, direction, or date filter. Null if the address has no transactions of its own.")
39
+ last_tx: Optional[TxSummary] = Field(default=None, description="Last transaction in which this address appears, over its entire history — independent of any neighbor, direction, or date filter. Null if the address has no transactions of its own.")
40
40
  in_degree: StrictInt
41
41
  out_degree: StrictInt
42
42
  no_incoming_txs: StrictInt
@@ -25,7 +25,7 @@ ENTITY_ANY_OF_SCHEMAS = ["Entity", "int"]
25
25
 
26
26
  class Entity(BaseModel):
27
27
  """
28
- Deprecated alias of `cluster`. Use `cluster` instead; this field is retained for backwards compatibility and will be removed in a future release.
28
+ Deprecated alias of `cluster`. Use `cluster` instead; this field is retained for backwards compatibility and will be removed in a future release. When expanded to an object, these are the neighbor cluster's own lifetime attributes, NOT relative to the queried cluster or the edge between them.
29
29
  """
30
30
 
31
31
  # data type: Entity
@@ -15,7 +15,7 @@ import pprint
15
15
  import re # noqa: F401
16
16
  import json
17
17
 
18
- from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
18
+ from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
19
19
  from typing import Any, ClassVar, Dict, List, Optional
20
20
  from graphsense.models.address import Address
21
21
  from graphsense.models.values import Values
@@ -27,10 +27,10 @@ class NeighborAddress(BaseModel):
27
27
  Neighbor address model.
28
28
  """ # noqa: E501
29
29
  value: Values
30
- no_txs: StrictInt
30
+ no_txs: StrictInt = Field(description="Number of transactions on the edge between the queried address and this neighbor (edge-scoped, not the neighbor's lifetime transaction count).")
31
31
  address: Address
32
32
  labels: Optional[List[StrictStr]] = None
33
- token_values: Optional[Dict[str, Values]] = None
33
+ token_values: Optional[Dict[str, Values]] = Field(default=None, description="Per-token value transferred on this edge (edge-scoped).")
34
34
  __properties: ClassVar[List[str]] = ["value", "no_txs", "address", "labels", "token_values"]
35
35
 
36
36
  model_config = ConfigDict(
@@ -15,7 +15,7 @@ import pprint
15
15
  import re # noqa: F401
16
16
  import json
17
17
 
18
- from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
18
+ from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
19
19
  from typing import Any, ClassVar, Dict, List, Optional
20
20
  from graphsense.models.cluster import Cluster
21
21
  from graphsense.models.entity import Entity
@@ -28,10 +28,10 @@ class NeighborCluster(BaseModel):
28
28
  Neighbor cluster (canonical name, supersedes `NeighborEntity`).
29
29
  """ # noqa: E501
30
30
  value: Values
31
- no_txs: StrictInt
31
+ no_txs: StrictInt = Field(description="Number of transactions on the edge between the queried cluster and this neighbor (edge-scoped, not the neighbor's lifetime transaction count).")
32
32
  entity: Optional[Entity] = None
33
33
  labels: Optional[List[StrictStr]] = None
34
- token_values: Optional[Dict[str, Values]] = None
34
+ token_values: Optional[Dict[str, Values]] = Field(default=None, description="Per-token value transferred on this edge (edge-scoped).")
35
35
  cluster: Optional[Cluster]
36
36
  __properties: ClassVar[List[str]] = ["value", "no_txs", "entity", "labels", "token_values", "cluster"]
37
37
 
@@ -15,7 +15,7 @@ import pprint
15
15
  import re # noqa: F401
16
16
  import json
17
17
 
18
- from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
18
+ from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
19
19
  from typing import Any, ClassVar, Dict, List, Optional
20
20
  from graphsense.models.cluster import Cluster
21
21
  from graphsense.models.entity import Entity
@@ -28,10 +28,10 @@ class NeighborEntity(BaseModel):
28
28
  Neighbor cluster model (legacy name: NeighborEntity). Dual-emits the neighbor reference under both `entity` (deprecated) and `cluster` (preferred) keys, mirroring the alias pattern on the top-level `Entity`/`Cluster` models. The value is either an integer ID or a full `Entity`/`Cluster` object; whichever shape `entity` carries, `cluster` carries the same value.
29
29
  """ # noqa: E501
30
30
  value: Values
31
- no_txs: StrictInt
31
+ no_txs: StrictInt = Field(description="Number of transactions on the edge between the queried cluster and this neighbor (edge-scoped, not the neighbor's lifetime transaction count).")
32
32
  entity: Optional[Entity] = None
33
33
  labels: Optional[List[StrictStr]] = None
34
- token_values: Optional[Dict[str, Values]] = None
34
+ token_values: Optional[Dict[str, Values]] = Field(default=None, description="Per-token value transferred on this edge (edge-scoped).")
35
35
  cluster: Optional[Cluster]
36
36
  __properties: ClassVar[List[str]] = ["value", "no_txs", "entity", "labels", "token_values", "cluster"]
37
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphsense-python
3
- Version: 2.15.0
3
+ Version: 2.15.2
4
4
  Summary: GraphSense API
5
5
  Author-email: Iknaio Cryptoasset Analytics GmbH <contact@iknaio.com>
6
6
  License-Expression: MIT
@@ -63,8 +63,8 @@ connector or tool source; the assistant then handles the rest.
63
63
 
64
64
  This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
65
65
 
66
- - API version: 2.15.0
67
- - Package version: 2.15.0
66
+ - API version: 2.15.2
67
+ - Package version: 2.15.2
68
68
  - Generator version: 7.19.0
69
69
  - Build package: org.openapitools.codegen.languages.PythonClientCodegen
70
70
  For more information, please visit [https://www.iknaio.com/](https://www.iknaio.com/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "graphsense-python"
3
- version = "2.15.0"
3
+ version = "2.15.2"
4
4
  description = "GraphSense API"
5
5
  license = "MIT"
6
6
  authors = [
@@ -0,0 +1,105 @@
1
+ """Bulk-vs-per-item dispatch heuristic and GraphSense.bulk()."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from graphsense.ext import bulk as bulk_mod
6
+ from graphsense.ext import GraphSense
7
+
8
+
9
+ def test_should_bulk_default_threshold():
10
+ assert not bulk_mod.should_bulk(5)
11
+ assert bulk_mod.should_bulk(10)
12
+ assert bulk_mod.should_bulk(100)
13
+
14
+
15
+ def test_should_bulk_override_true():
16
+ assert bulk_mod.should_bulk(1, override=True)
17
+
18
+
19
+ def test_should_bulk_override_false():
20
+ assert not bulk_mod.should_bulk(1000, override=False)
21
+
22
+
23
+ def test_run_parallel_preserves_order():
24
+ def f(x):
25
+ return x * 2
26
+
27
+ assert bulk_mod.run_parallel(f, [1, 2, 3, 4]) == [2, 4, 6, 8]
28
+
29
+
30
+ def test_gs_bulk_posts_to_json_endpoint(gs: GraphSense, http_mock):
31
+ http_mock.add(
32
+ "POST",
33
+ "/btc/bulk.json/get_address",
34
+ json_body=[{"address": "1A", "balance": 1}],
35
+ )
36
+ gs.bulk("get_address", ["1A", "1B"])
37
+ assert any("/btc/bulk.json/get_address" in c.url for c in http_mock.calls)
38
+ # body should contain the key list
39
+ call = [c for c in http_mock.calls if "/bulk.json" in c.url][0]
40
+ body = call.body
41
+ assert "1A" in str(body)
42
+ assert "1B" in str(body)
43
+
44
+
45
+ def test_gs_bulk_csv_hits_csv_endpoint(gs: GraphSense, http_mock):
46
+ http_mock.add(
47
+ "POST",
48
+ "/btc/bulk.csv/get_address",
49
+ body="address,balance\n1A,1\n",
50
+ headers={"content-type": "text/csv"},
51
+ )
52
+ gs.bulk("get_address", ["1A"], format="csv")
53
+ assert any("/btc/bulk.csv/get_address" in c.url for c in http_mock.calls)
54
+
55
+
56
+ def test_chunked_splits_evenly_and_keeps_remainder():
57
+ assert list(bulk_mod.chunked([1, 2, 3, 4, 5], 2)) == [[1, 2], [3, 4], [5]]
58
+ assert list(bulk_mod.chunked([], 2)) == []
59
+
60
+
61
+ def test_merge_json_chunks_concatenates_rows():
62
+ merged = bulk_mod.merge_json_chunks([[{"a": 1}], [{"a": 2}, {"a": 3}], None])
63
+ assert merged == [{"a": 1}, {"a": 2}, {"a": 3}]
64
+
65
+
66
+ def test_merge_csv_chunks_unions_headers():
67
+ """Each chunk gets its own server-side header inference, so a later chunk
68
+ may carry a column the first one never saw. Nothing may be dropped."""
69
+ first = "address,balance\n1A,1\n"
70
+ second = "address,balance,token\n1B,2,usdt\n"
71
+ merged = bulk_mod.merge_csv_chunks([first, second])
72
+ lines = merged.strip().splitlines()
73
+ assert lines[0] == "address,balance,token"
74
+ assert lines[1] == "1A,1,"
75
+ assert lines[2] == "1B,2,usdt"
76
+
77
+
78
+ def test_gs_bulk_splits_long_key_lists(gs: GraphSense, http_mock):
79
+ """A key list longer than the server's per-request cap is sent as several
80
+ requests instead of one oversized (and rejected) request."""
81
+ http_mock.add(
82
+ "POST",
83
+ "/btc/bulk.json/get_address",
84
+ json_body=[{"address": "1A", "balance": 1}],
85
+ )
86
+ keys = [f"addr{i}" for i in range(7)]
87
+ result = gs.bulk("get_address", keys, chunk_size=3)
88
+
89
+ calls = [c for c in http_mock.calls if "/bulk.json" in c.url]
90
+ assert len(calls) == 3
91
+ sent = [str(c.body) for c in calls]
92
+ assert "addr0" in sent[0] and "addr2" in sent[0] and "addr3" not in sent[0]
93
+ assert "addr6" in sent[2]
94
+ # every chunk's rows come back concatenated
95
+ assert len(result) == 3
96
+
97
+
98
+ def test_gs_bulk_sends_one_request_below_the_chunk_size(gs: GraphSense, http_mock):
99
+ http_mock.add(
100
+ "POST",
101
+ "/btc/bulk.json/get_address",
102
+ json_body=[{"address": "1A", "balance": 1}],
103
+ )
104
+ gs.bulk("get_address", ["1A", "1B"], chunk_size=100)
105
+ assert len([c for c in http_mock.calls if "/bulk.json" in c.url]) == 1