tcex 5.0.0.dev0__tar.gz → 5.0.0.dev1__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 (388) hide show
  1. {tcex-5.0.0.dev0/tcex.egg-info → tcex-5.0.0.dev1}/PKG-INFO +1 -1
  2. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/pyproject.toml +17 -26
  3. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/__metadata__.py +1 -1
  4. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/ti_transform/ti_transform.py +0 -2
  5. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/__init__.py +2 -1
  6. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/association.py +0 -1
  7. tcex-5.0.0.dev1/tcex/api/tc/v2/batch/batch_cleaner.py +449 -0
  8. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/batch_submit.py +50 -131
  9. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_gen_object_abc.py +45 -2
  10. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/attribute_types/attribute_type.py +10 -0
  11. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/cases/case.py +1 -1
  12. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/groups/group_filter.py +10 -0
  13. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owner_roles/owner_role.py +10 -0
  14. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owners/owner.py +8 -0
  15. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/system_roles/system_role.py +10 -0
  16. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/user_groups/user_group.py +10 -0
  17. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/users/user.py +10 -0
  18. tcex-5.0.0.dev1/tcex/api/tc/v3/tags/mitre_tags.py +157 -0
  19. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tags/naics_tags.py +11 -3
  20. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tasks/task.py +1 -1
  21. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/threat_intelligence/threat_intelligence.py +6 -18
  22. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/app_spec_yml.py +4 -0
  23. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/install_json.py +1 -1
  24. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/install_json_update.py +3 -3
  25. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/install_json_validate.py +2 -2
  26. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/model/app_spec_yml_model.py +6 -4
  27. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/model/install_json_model.py +16 -15
  28. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/permutation.py +1 -1
  29. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/service/mqtt_message_broker.py +4 -6
  30. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/input.py +19 -0
  31. tcex-5.0.0.dev1/tcex/pleb/cached_property_filesystem.py +276 -0
  32. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/code_operation.py +18 -5
  33. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1/tcex.egg-info}/PKG-INFO +1 -1
  34. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex.egg-info/SOURCES.txt +2 -0
  35. tcex-5.0.0.dev0/tcex/api/tc/v3/tags/mitre_tags.py +0 -76
  36. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/LICENSE +0 -0
  37. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/README.md +0 -0
  38. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/setup.cfg +0 -0
  39. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/__init__.py +0 -0
  40. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/__init__.py +0 -0
  41. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/api.py +0 -0
  42. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/__init__.py +0 -0
  43. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/tc.py +0 -0
  44. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/ti_transform/__init__.py +0 -0
  45. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/ti_transform/model/__init__.py +0 -0
  46. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/ti_transform/model/transform_model.py +0 -0
  47. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/ti_transform/ti_predefined_functions.py +0 -0
  48. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/ti_transform/transform_abc.py +0 -0
  49. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/util/__init__.py +0 -0
  50. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/util/threat_intel_util.py +0 -0
  51. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/__init__.py +0 -0
  52. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/attribute.py +0 -0
  53. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/batch.py +0 -0
  54. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/batch_writer.py +0 -0
  55. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/group.py +0 -0
  56. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/indicator.py +0 -0
  57. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/security_label.py +0 -0
  58. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/batch/tag.py +0 -0
  59. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/datastore/__init__.py +0 -0
  60. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/datastore/cache.py +0 -0
  61. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/datastore/datastore.py +0 -0
  62. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/metric/__init__.py +0 -0
  63. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/metric/metric.py +0 -0
  64. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/notification/__init__.py +0 -0
  65. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/notification/notification.py +0 -0
  66. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/__init__.py +0 -0
  67. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/__init__.py +0 -0
  68. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/filters.py +0 -0
  69. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/__init__.py +0 -0
  70. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group.py +0 -0
  71. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/__init__.py +0 -0
  72. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/adversary.py +0 -0
  73. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/attack_pattern.py +0 -0
  74. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/campaign.py +0 -0
  75. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/course_of_action.py +0 -0
  76. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/document.py +0 -0
  77. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/email.py +0 -0
  78. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/event.py +0 -0
  79. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/incident.py +0 -0
  80. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/intrusion_set.py +0 -0
  81. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/malware.py +0 -0
  82. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/report.py +0 -0
  83. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/signature.py +0 -0
  84. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/tactic.py +0 -0
  85. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/threat.py +0 -0
  86. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/tool.py +0 -0
  87. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/vulnerability.py +0 -0
  88. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/__init__.py +0 -0
  89. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator.py +0 -0
  90. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/__init__.py +0 -0
  91. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/address.py +0 -0
  92. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/email_address.py +0 -0
  93. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/file.py +0 -0
  94. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/host.py +0 -0
  95. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/url.py +0 -0
  96. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/mapping.py +0 -0
  97. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/owner.py +0 -0
  98. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/security_label.py +0 -0
  99. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/tag.py +0 -0
  100. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/tags.py +0 -0
  101. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/task.py +0 -0
  102. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/mapping/victim.py +0 -0
  103. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/tcex_ti_tc_request.py +0 -0
  104. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/threat_intelligence/threat_intelligence.py +0 -0
  105. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v2/v2.py +0 -0
  106. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/__init__.py +0 -0
  107. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/__init__.py +0 -0
  108. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_gen.py +0 -0
  109. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_gen_abc.py +0 -0
  110. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_gen_args_abc.py +0 -0
  111. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_gen_filter_abc.py +0 -0
  112. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_gen_model_abc.py +0 -0
  113. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/_options_data.py +0 -0
  114. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/model/__init__.py +0 -0
  115. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/model/_extra_model.py +0 -0
  116. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/model/_filter_model.py +0 -0
  117. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/_gen/model/_property_model.py +0 -0
  118. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/adversary_assets/__init__.py +0 -0
  119. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/api_endpoints.py +0 -0
  120. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifact_types/__init__.py +0 -0
  121. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifact_types/artifact_type.py +0 -0
  122. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifact_types/artifact_type_filter.py +0 -0
  123. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifact_types/artifact_type_model.py +0 -0
  124. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifacts/__init__.py +0 -0
  125. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifacts/artifact.py +0 -0
  126. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifacts/artifact_filter.py +0 -0
  127. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/artifacts/artifact_model.py +0 -0
  128. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/attribute_types/__init__.py +0 -0
  129. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/attribute_types/attribute_type_filter.py +0 -0
  130. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/attribute_types/attribute_type_model.py +0 -0
  131. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/attributes/__init__.py +0 -0
  132. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/attributes/attribute_model.py +0 -0
  133. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/case_attributes/__init__.py +0 -0
  134. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/case_attributes/case_attribute.py +0 -0
  135. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/case_attributes/case_attribute_filter.py +0 -0
  136. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/case_attributes/case_attribute_model.py +0 -0
  137. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/case_management/__init__.py +0 -0
  138. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/case_management/case_management.py +0 -0
  139. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/cases/__init__.py +0 -0
  140. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/cases/case_filter.py +0 -0
  141. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/cases/case_model.py +0 -0
  142. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/file_actions/__init__.py +0 -0
  143. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/file_actions/file_action_model.py +0 -0
  144. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/file_occurrences/__init__.py +0 -0
  145. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/file_occurrences/file_occurrence_model.py +0 -0
  146. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/filter_abc.py +0 -0
  147. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/group_attributes/__init__.py +0 -0
  148. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/group_attributes/group_attribute.py +0 -0
  149. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/group_attributes/group_attribute_filter.py +0 -0
  150. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/group_attributes/group_attribute_model.py +0 -0
  151. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/groups/__init__.py +0 -0
  152. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/groups/group.py +0 -0
  153. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/groups/group_model.py +0 -0
  154. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicator_attributes/__init__.py +0 -0
  155. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicator_attributes/indicator_attribute.py +0 -0
  156. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicator_attributes/indicator_attribute_filter.py +0 -0
  157. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicator_attributes/indicator_attribute_model.py +0 -0
  158. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicators/__init__.py +0 -0
  159. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicators/indicator.py +0 -0
  160. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicators/indicator_filter.py +0 -0
  161. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/indicators/indicator_model.py +0 -0
  162. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirement/__init__.py +0 -0
  163. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirement/ir.py +0 -0
  164. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/__init__.py +0 -0
  165. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/categories/__init__.py +0 -0
  166. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/categories/category.py +0 -0
  167. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/categories/category_filter.py +0 -0
  168. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/categories/category_model.py +0 -0
  169. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/intel_req_type_model.py +0 -0
  170. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/intel_requirement.py +0 -0
  171. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/intel_requirement_filter.py +0 -0
  172. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/intel_requirement_model.py +0 -0
  173. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/keyword_sections/__init__.py +0 -0
  174. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/keyword_sections/keyword_section_model.py +0 -0
  175. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/results/__init__.py +0 -0
  176. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/results/result.py +0 -0
  177. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/results/result_filter.py +0 -0
  178. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/results/result_model.py +0 -0
  179. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/subtypes/__init__.py +0 -0
  180. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/subtypes/subtype.py +0 -0
  181. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/subtypes/subtype_filter.py +0 -0
  182. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/intel_requirements/subtypes/subtype_model.py +0 -0
  183. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/notes/__init__.py +0 -0
  184. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/notes/note.py +0 -0
  185. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/notes/note_filter.py +0 -0
  186. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/notes/note_model.py +0 -0
  187. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/object_abc.py +0 -0
  188. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/object_collection_abc.py +0 -0
  189. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/__init__.py +0 -0
  190. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/assignee_model.py +0 -0
  191. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/assignee_user_group_model.py +0 -0
  192. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/assignee_user_model.py +0 -0
  193. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/exclusion_lists/__init__.py +0 -0
  194. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list.py +0 -0
  195. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list_filter.py +0 -0
  196. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list_model.py +0 -0
  197. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owner_roles/__init__.py +0 -0
  198. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owner_roles/owner_role_filter.py +0 -0
  199. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owner_roles/owner_role_model.py +0 -0
  200. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owners/__init__.py +0 -0
  201. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owners/owner_filter.py +0 -0
  202. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/owners/owner_model.py +0 -0
  203. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/security.py +0 -0
  204. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/system_roles/__init__.py +0 -0
  205. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/system_roles/system_role_filter.py +0 -0
  206. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/system_roles/system_role_model.py +0 -0
  207. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/task_assignee_model.py +0 -0
  208. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/user_groups/__init__.py +0 -0
  209. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/user_groups/user_group_filter.py +0 -0
  210. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/user_groups/user_group_model.py +0 -0
  211. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/users/__init__.py +0 -0
  212. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/users/user_filter.py +0 -0
  213. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security/users/user_model.py +0 -0
  214. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security_labels/__init__.py +0 -0
  215. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security_labels/security_label.py +0 -0
  216. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security_labels/security_label_filter.py +0 -0
  217. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/security_labels/security_label_model.py +0 -0
  218. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tags/__init__.py +0 -0
  219. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tags/default_naics_tags.py +0 -0
  220. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tags/tag.py +0 -0
  221. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tags/tag_filter.py +0 -0
  222. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tags/tag_model.py +0 -0
  223. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tasks/__init__.py +0 -0
  224. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tasks/task_filter.py +0 -0
  225. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tasks/task_model.py +0 -0
  226. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/threat_intelligence/__init__.py +0 -0
  227. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tql/__init__.py +0 -0
  228. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tql/tql.py +0 -0
  229. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tql/tql_operator.py +0 -0
  230. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/tql/tql_type.py +0 -0
  231. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/v3.py +0 -0
  232. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/v3_model_abc.py +0 -0
  233. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/v3_types.py +0 -0
  234. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_assets/__init__.py +0 -0
  235. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_assets/victim_asset.py +0 -0
  236. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_assets/victim_asset_filter.py +0 -0
  237. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_assets/victim_asset_model.py +0 -0
  238. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_attributes/__init__.py +0 -0
  239. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_attributes/victim_attribute.py +0 -0
  240. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_attributes/victim_attribute_filter.py +0 -0
  241. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victim_attributes/victim_attribute_model.py +0 -0
  242. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victims/__init__.py +0 -0
  243. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victims/victim.py +0 -0
  244. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victims/victim_filter.py +0 -0
  245. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/victims/victim_model.py +0 -0
  246. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_events/__init__.py +0 -0
  247. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_events/workflow_event.py +0 -0
  248. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_events/workflow_event_filter.py +0 -0
  249. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_events/workflow_event_model.py +0 -0
  250. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_templates/__init__.py +0 -0
  251. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_templates/workflow_template.py +0 -0
  252. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_templates/workflow_template_filter.py +0 -0
  253. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/api/tc/v3/workflow_templates/workflow_template_model.py +0 -0
  254. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/__init__.py +0 -0
  255. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/app.py +0 -0
  256. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/__init__.py +0 -0
  257. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/job_json.py +0 -0
  258. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/layout_json.py +0 -0
  259. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/model/__init__.py +0 -0
  260. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/model/job_json_model.py +0 -0
  261. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/model/layout_json_model.py +0 -0
  262. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/model/tcex_json_model.py +0 -0
  263. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/tcex_json.py +0 -0
  264. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/config/tcex_json_update.py +0 -0
  265. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/__init__.py +0 -0
  266. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/benchmark.py +0 -0
  267. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/debug.py +0 -0
  268. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/fail_on_output.py +0 -0
  269. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/on_exception.py +0 -0
  270. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/on_success.py +0 -0
  271. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/decorator/output.py +0 -0
  272. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/__init__.py +0 -0
  273. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/key_value_abc.py +0 -0
  274. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/key_value_api.py +0 -0
  275. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/key_value_mock.py +0 -0
  276. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/key_value_redis.py +0 -0
  277. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/key_value_store.py +0 -0
  278. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/redis_client.py +0 -0
  279. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/key_value_store/redis_client_ssl.py +0 -0
  280. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/__init__.py +0 -0
  281. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/advanced_request.py +0 -0
  282. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/playbook.py +0 -0
  283. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/playbook_create.py +0 -0
  284. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/playbook_delete.py +0 -0
  285. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/playbook_output.py +0 -0
  286. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/playbook/playbook_read.py +0 -0
  287. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/service/__init__.py +0 -0
  288. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/service/api_service.py +0 -0
  289. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/service/common_service.py +0 -0
  290. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/service/common_service_trigger.py +0 -0
  291. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/service/webhook_trigger_service.py +0 -0
  292. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/token/__init__.py +0 -0
  293. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/app/token/token.py +0 -0
  294. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/exit/__init__.py +0 -0
  295. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/exit/error_code.py +0 -0
  296. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/exit/exit.py +0 -0
  297. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/__init__.py +0 -0
  298. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/__init__.py +0 -0
  299. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/binary.py +0 -0
  300. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/case_management_entity.py +0 -0
  301. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/choice.py +0 -0
  302. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/datetime.py +0 -0
  303. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/edit_choice.py +0 -0
  304. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/exception.py +0 -0
  305. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/group_entity.py +0 -0
  306. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/indicator_entity.py +0 -0
  307. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/integer.py +0 -0
  308. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/ip_address.py +0 -0
  309. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/key_value.py +0 -0
  310. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/sensitive.py +0 -0
  311. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/string.py +0 -0
  312. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/tc_entity.py +0 -0
  313. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/field_type/validator.py +0 -0
  314. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/__init__.py +0 -0
  315. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/advanced_request_model.py +0 -0
  316. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/api_model.py +0 -0
  317. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_api_service_model.py +0 -0
  318. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_external_model.py +0 -0
  319. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_feed_api_service_model.py +0 -0
  320. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_organization_model.py +0 -0
  321. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_playbook_model.py +0 -0
  322. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_system_model.py +0 -0
  323. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_trigger_service_model.py +0 -0
  324. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/app_webhook_trigger_service_model.py +0 -0
  325. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/batch_model.py +0 -0
  326. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/cal_setting_model.py +0 -0
  327. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/cert_model.py +0 -0
  328. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/common_advanced_model.py +0 -0
  329. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/common_model.py +0 -0
  330. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/create_config_model.py +0 -0
  331. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/logging_model.py +0 -0
  332. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/model_map.py +0 -0
  333. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/module_app_model.py +0 -0
  334. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/module_requests_session_model.py +0 -0
  335. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/organization_model.py +0 -0
  336. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/path_model.py +0 -0
  337. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/playbook_common_model.py +0 -0
  338. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/playbook_model.py +0 -0
  339. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/proxy_model.py +0 -0
  340. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/service_model.py +0 -0
  341. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/input/model/smtp_setting_model.py +0 -0
  342. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/__init__.py +0 -0
  343. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/api_handler.py +0 -0
  344. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/cache_handler.py +0 -0
  345. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/logger.py +0 -0
  346. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/rotating_file_handler_custom.py +0 -0
  347. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/sensitive_filter.py +0 -0
  348. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/logger/trace_logger.py +0 -0
  349. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/__init__.py +0 -0
  350. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/cached_property.py +0 -0
  351. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/event.py +0 -0
  352. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/exception_thread.py +0 -0
  353. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/jmespath_custom.py +0 -0
  354. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/none_model.py +0 -0
  355. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/proxies.py +0 -0
  356. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/scoped_property.py +0 -0
  357. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/pleb/singleton.py +0 -0
  358. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/registry.py +0 -0
  359. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_external/__init__.py +0 -0
  360. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_external/external_session.py +0 -0
  361. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_external/rate_limit_handler.py +0 -0
  362. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_external/requests_external.py +0 -0
  363. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/__init__.py +0 -0
  364. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/auth/__init__.py +0 -0
  365. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/auth/hmac_auth.py +0 -0
  366. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/auth/tc_auth.py +0 -0
  367. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/auth/token_auth.py +0 -0
  368. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/requests_tc.py +0 -0
  369. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/requests_tc/tc_session.py +0 -0
  370. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/tcex.py +0 -0
  371. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/__init__.py +0 -0
  372. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/aes_operation.py +0 -0
  373. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/datetime_operation.py +0 -0
  374. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/file_operation.py +0 -0
  375. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/model/__init__.py +0 -0
  376. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/model/playbook_variable_model.py +0 -0
  377. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/render/__init__.py +0 -0
  378. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/render/render.py +0 -0
  379. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/render/render_panel.py +0 -0
  380. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/render/render_prompt.py +0 -0
  381. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/render/render_table.py +0 -0
  382. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/requests_to_curl.py +0 -0
  383. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/string_operation.py +0 -0
  384. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/util.py +0 -0
  385. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex/util/variable.py +0 -0
  386. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex.egg-info/dependency_links.txt +0 -0
  387. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex.egg-info/requires.txt +0 -0
  388. {tcex-5.0.0.dev0 → tcex-5.0.0.dev1}/tcex.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tcex
3
- Version: 5.0.0.dev0
3
+ Version: 5.0.0.dev1
4
4
  Summary: ThreatConnect Exchange App Framework
5
5
  Author-email: ThreatConnect <support@threatconnect.com>
6
6
  License-Expression: Apache-2.0
@@ -86,6 +86,7 @@ test = [
86
86
  "pytest-html>=4.1.1",
87
87
  "pytest-ordering>=0.6",
88
88
  "pytest-xdist>=3.6.1",
89
+ "python-dotenv>=1.2.1",
89
90
  ]
90
91
 
91
92
  # Dynamic configuration for setuptools
@@ -101,16 +102,6 @@ exclude = ["tests*", "local-*"]
101
102
  # Tool configurations
102
103
  #
103
104
 
104
- # This must remain while code_operations still uses isort directly.
105
- [tool.isort]
106
- dedup_headings = false
107
- include_trailing_comma = true
108
- known_first_party = ""
109
- known_local_folder = "."
110
- known_third_party = ""
111
- line_length = 100
112
- profile = "black"
113
-
114
105
  [tool.uv.extra-build-dependencies]
115
106
  tcex = [
116
107
  "arrow>=1.3.0",
@@ -138,7 +129,7 @@ commit = false
138
129
  # Additional arguments to pass to the git commit command
139
130
  commit_args = "--no-verify"
140
131
  # The current version of the project
141
- current_version = "5.0.0-dev0"
132
+ current_version = "5.0.0-dev1"
142
133
  # The commit message template used when committing version bumps
143
134
  message = "Version updated from {current_version} to {new_version}"
144
135
  # Regular expression for parsing version strings
@@ -364,18 +355,18 @@ split-on-trailing-comma = true
364
355
 
365
356
  convention = "google"
366
357
 
367
- [tool.cibuildwheel]
368
- # You can also put cibuildwheel config here instead of env vars
369
- build = "cp38-* cp39-* cp310-* cp311-* cp312-*"
370
- skip = "*-win32 *-manylinux_i686 *-musllinux_i686 pp*"
371
-
372
- [tool.cibuildwheel.linux]
373
- before-build = "pip install -U pip setuptools wheel"
374
- repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
375
-
376
- [tool.cibuildwheel.macos]
377
- before-build = "pip install -U pip setuptools wheel"
378
- repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
379
-
380
- [tool.cibuildwheel.windows]
381
- before-build = "pip install -U pip setuptools wheel"
358
+ # [tool.cibuildwheel]
359
+ # # You can also put cibuildwheel config here instead of env vars
360
+ # build = "cp38-* cp39-* cp310-* cp311-* cp312-*"
361
+ # skip = "*-win32 *-manylinux_i686 *-musllinux_i686 pp*"
362
+ #
363
+ # [tool.cibuildwheel.linux]
364
+ # before-build = "pip install -U pip setuptools wheel"
365
+ # repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
366
+ #
367
+ # [tool.cibuildwheel.macos]
368
+ # before-build = "pip install -U pip setuptools wheel"
369
+ # repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
370
+ #
371
+ # [tool.cibuildwheel.windows]
372
+ # before-build = "pip install -U pip setuptools wheel"
@@ -1,4 +1,4 @@
1
1
  """TcEx Framework Module"""
2
2
 
3
3
  __license__ = 'Apache-2.0'
4
- __version__ = '5.0.0-dev0'
4
+ __version__ = '5.0.0-dev1'
@@ -1,9 +1,7 @@
1
1
  """TcEx Framework Module"""
2
2
 
3
- # standard library
4
3
  from datetime import datetime
5
4
 
6
- # first-party
7
5
  from tcex.api.tc.ti_transform.model import GroupTransformModel, IndicatorTransformModel
8
6
  from tcex.api.tc.ti_transform.transform_abc import (
9
7
  AssociationTransformModel,
@@ -1,7 +1,8 @@
1
1
  """TcEx Framework Module"""
2
2
 
3
3
  from tcex.api.tc.v2.batch.batch import Batch
4
+ from tcex.api.tc.v2.batch.batch_cleaner import BatchCleaner
4
5
  from tcex.api.tc.v2.batch.batch_submit import BatchSubmit
5
6
  from tcex.api.tc.v2.batch.batch_writer import BatchWriter, GroupType, IndicatorType
6
7
 
7
- __all__ = ['Batch', 'BatchSubmit', 'BatchWriter', 'GroupType', 'IndicatorType']
8
+ __all__ = ['Batch', 'BatchCleaner', 'BatchSubmit', 'BatchWriter', 'GroupType', 'IndicatorType']
@@ -1,6 +1,5 @@
1
1
  """TcEx Framework Module"""
2
2
 
3
- # standard library
4
3
  import json
5
4
 
6
5
 
@@ -0,0 +1,449 @@
1
+ """TcEx Framework Module"""
2
+
3
+ import json
4
+ import logging
5
+ from collections.abc import Callable
6
+ from itertools import chain
7
+
8
+ from tcex.api.tc.v3.tags.mitre_tags import MitreTags
9
+ from tcex.api.tc.v3.tags.naics_tags import NAICSTags
10
+ from tcex.logger.trace_logger import TraceLogger
11
+ from tcex.pleb.cached_property import cached_property
12
+ from tcex.util.datetime_operation import DatetimeOperation
13
+
14
+ # get tcex logger
15
+ _logger: TraceLogger = logging.getLogger(__name__.split('.', maxsplit=1)[0]) # type: ignore
16
+
17
+
18
+ class BatchCleaner:
19
+ """Batch content cleaning configuration and logic.
20
+
21
+ All flags default to False. Enable the desired cleaning steps before calling clean().
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ attribute_types: dict[str, dict],
27
+ mitre_tags: MitreTags,
28
+ *,
29
+ combine_on_filename: bool = False,
30
+ convert_to_mitre_tags: bool = False,
31
+ convert_to_naics_tags: bool = False,
32
+ deduplicate_indicators: bool = False,
33
+ deduplicate_groups: bool = False,
34
+ deduplicate_attributes: bool = False,
35
+ truncate_attributes: bool = False,
36
+ ):
37
+ """Initialize instance properties.
38
+
39
+ Args:
40
+ attribute_types: A dictionary of attribute type config.
41
+ mitre_tags: A MitreTags instance.
42
+ combine_on_filename: Whether to combine items based on filename.
43
+ convert_to_mitre_tags: Whether to convert Mitre tags.
44
+ convert_to_naics_tags: Whether to convert NAICS tags.
45
+ deduplicate_indicators: Whether to deduplicate indicators.
46
+ deduplicate_groups: Whether to deduplicate groups.
47
+ deduplicate_attributes: Whether to deduplicate attributes.
48
+ truncate_attributes: Whether to truncate attributes.
49
+ """
50
+ self.attribute_types = attribute_types
51
+ self.mitre_tags = mitre_tags
52
+
53
+ self.combine_on_filename = combine_on_filename
54
+ self.convert_to_mitre_tags = convert_to_mitre_tags
55
+ self.convert_to_naics_tags = convert_to_naics_tags
56
+ self.deduplicate_indicators = deduplicate_indicators
57
+ self.deduplicate_groups = deduplicate_groups
58
+ self.deduplicate_attributes = deduplicate_attributes
59
+ self.truncate_attributes = truncate_attributes
60
+
61
+ def clean(self, content: dict | str) -> dict:
62
+ """Run enabled cleaning steps on content.
63
+
64
+ Pass 1: Deduplicate and combine indicators/groups.
65
+ Pass 2: Clean attributes (truncate/deduplicate) on the result.
66
+
67
+ Args:
68
+ content: The batch content dictionary or JSON string.
69
+
70
+ Returns:
71
+ The cleaned content dictionary.
72
+ """
73
+ content_dict = json.loads(content) if isinstance(content, str) else content
74
+
75
+ # Pass 1: dedup/combine
76
+ content_dict['indicator'] = self._dedup_indicators(content_dict.get('indicator', []))
77
+ if self.deduplicate_groups:
78
+ content_dict['group'] = self._dedup(content_dict.get('group', []), self._group_keys)
79
+
80
+ # Pass 2: attribute cleaning
81
+ if self.deduplicate_attributes or self.truncate_attributes:
82
+ truncated_types: set[str] = set()
83
+ for item in chain(
84
+ content_dict.get('indicator') or [],
85
+ content_dict.get('group') or [],
86
+ ):
87
+ self.clean_attributes(
88
+ item,
89
+ self.attribute_types,
90
+ deduplicate=self.deduplicate_attributes,
91
+ truncate=self.truncate_attributes,
92
+ truncated_types=truncated_types,
93
+ )
94
+
95
+ return content_dict
96
+
97
+ def _dedup_indicators(self, indicators: list[dict]) -> list[dict]:
98
+ """Deduplicate and combine indicators.
99
+
100
+ When deduplicate_indicators is enabled, indicators are matched by
101
+ summary (hash-overlap for File indicators, exact match for others).
102
+
103
+ When combine_on_filename is enabled, File indicators with matching
104
+ fileOccurrence fileName values are also merged.
105
+
106
+ Args:
107
+ indicators: The list of indicator dictionaries.
108
+
109
+ Returns:
110
+ The deduplicated list of indicator dictionaries.
111
+ """
112
+ if not self.deduplicate_indicators and not self.combine_on_filename:
113
+ return indicators
114
+
115
+ return self._dedup(indicators, self._indicator_keys)
116
+
117
+ def _dedup(
118
+ self,
119
+ items: list[dict],
120
+ key_fn: Callable[[dict], set[str]],
121
+ ) -> list[dict]:
122
+ """Deduplicate items using a key function that returns a set of lookup keys.
123
+
124
+ Each item is matched against previously seen keys. If any key from the
125
+ incoming item is already in the index, the item is merged into the
126
+ existing one. After a merge, keys are re-indexed to account for new
127
+ keys added during the merge (e.g. File hash combination).
128
+
129
+ Args:
130
+ items: The list of items to deduplicate.
131
+ key_fn: A callable that takes an item dict and returns a set of
132
+ strings to use as lookup keys. Return an empty set for items
133
+ that should never be deduplicated.
134
+
135
+ Returns:
136
+ The deduplicated list of items.
137
+ """
138
+ result: list[dict] = []
139
+ seen: dict[str, int] = {}
140
+
141
+ for item in items:
142
+ keys = key_fn(item)
143
+ match_idx = next((seen[k] for k in keys if k in seen), None)
144
+ if match_idx is not None:
145
+ self.deduplicate(item, result[match_idx])
146
+ # re-index: merged item may have new keys (e.g. combined file hashes)
147
+ for k in key_fn(result[match_idx]):
148
+ seen[k] = match_idx
149
+ else:
150
+ idx = len(result)
151
+ for k in keys:
152
+ seen[k] = idx
153
+ result.append(item)
154
+
155
+ return result
156
+
157
+ def _indicator_keys(self, item: dict) -> set[str]:
158
+ """Return the set of dedup keys for an indicator.
159
+
160
+ File indicators return individual hashes so that any hash overlap
161
+ triggers a merge. When combine_on_filename is enabled, a prefixed
162
+ filename key is also included for File indicators with a fileOccurrence.
163
+
164
+ All other indicators return the summary as a single-element set.
165
+
166
+ Args:
167
+ item: An indicator dictionary.
168
+
169
+ Returns:
170
+ A set of key strings for dedup lookup.
171
+ """
172
+ if item.get('type') == 'File':
173
+ keys: set[str] = set()
174
+ if self.deduplicate_indicators:
175
+ keys = BatchCleaner._file_hash_set(item.get('summary', ''))
176
+ if self.combine_on_filename:
177
+ file_occ = item.get('fileOccurrence') or []
178
+ if file_occ:
179
+ fname = file_occ[0].get('fileName')
180
+ if fname:
181
+ keys.add(f'filename:{fname.lower()}')
182
+ return keys
183
+ if self.deduplicate_indicators:
184
+ summary = item.get('summary')
185
+ return {summary} if summary else set()
186
+ return set()
187
+
188
+ @staticmethod
189
+ def _group_keys(item: dict) -> set[str]:
190
+ """Return the set of dedup keys for a group.
191
+
192
+ Groups are deduplicated by xid.
193
+
194
+ Args:
195
+ item: A group dictionary.
196
+
197
+ Returns:
198
+ A single-element set containing the xid, or empty set if no xid.
199
+ """
200
+ xid = item.get('xid')
201
+ return {xid} if xid else set()
202
+
203
+ def _normalize_tags(self, tags: list) -> None:
204
+ """Convert tag names to formatted MITRE/NAICS format where applicable.
205
+
206
+ Args:
207
+ tags: A list of tags
208
+ """
209
+ for tag in tags:
210
+ name = tag.get('name')
211
+ if not name:
212
+ continue
213
+
214
+ # try MITRE conversion first
215
+ if self.convert_to_mitre_tags:
216
+ formatted = self.mitre_tags.get_by_id_regex(name)
217
+ if formatted:
218
+ tag['name'] = formatted
219
+ continue
220
+
221
+ # try NAICS conversion
222
+ if self.convert_to_naics_tags:
223
+ formatted = self.naics_tags.get_by_id(name)
224
+ if formatted:
225
+ tag['name'] = formatted
226
+
227
+ @staticmethod
228
+ def auto_truncate_attribute(
229
+ attribute_type: str,
230
+ attribute_value: str,
231
+ attribute_config: dict,
232
+ ellipsis: str = '...',
233
+ ) -> str:
234
+ """Truncate attribute value if it exceeds the maximum length for its type.
235
+
236
+ Args:
237
+ attribute_type: The name of the attribute type.
238
+ attribute_value: The attribute value to potentially truncate.
239
+ attribute_config: Attribute type config dict keyed by name.
240
+ ellipsis: The string to append when truncating. Defaults to '...'.
241
+
242
+ Returns:
243
+ The original value if within limits, or the truncated value with ellipsis.
244
+ """
245
+ config = attribute_config.get(attribute_type)
246
+ if not config:
247
+ return attribute_value
248
+
249
+ max_length = config.get('maxSize')
250
+ if not max_length:
251
+ return attribute_value
252
+
253
+ if not isinstance(attribute_value, str):
254
+ return attribute_value
255
+
256
+ if len(attribute_value) <= max_length:
257
+ return attribute_value
258
+
259
+ if max_length <= len(ellipsis):
260
+ return attribute_value[:max_length]
261
+
262
+ return attribute_value[: max_length - len(ellipsis)] + ellipsis
263
+
264
+ @staticmethod
265
+ def clean_attributes(
266
+ item: dict,
267
+ attribute_types: dict,
268
+ deduplicate: bool = False,
269
+ truncate: bool = False,
270
+ truncated_types: set[str] | None = None,
271
+ ) -> dict:
272
+ """Truncate and/or deduplicate attributes on a single item.
273
+
274
+ Args:
275
+ item: A single group or indicator dictionary.
276
+ attribute_types: Attribute type config dict keyed by name.
277
+ deduplicate: Whether to remove duplicate attributes.
278
+ truncate: Whether to truncate attributes based on maxSize.
279
+ truncated_types: A shared set tracking attribute types that have already
280
+ been logged as truncated, to avoid duplicate log warnings. If None,
281
+ warnings are logged for every truncation.
282
+
283
+ Returns:
284
+ The item with cleaned attributes.
285
+ """
286
+ original_attrs = item.get('attribute') or []
287
+ cleaned_attrs = []
288
+ seen: set[tuple] = set()
289
+
290
+ for attr in original_attrs:
291
+ type_ = attr.get('type')
292
+ value = attr.get('value')
293
+
294
+ if not type_ or value is None or value == '':
295
+ continue
296
+
297
+ if truncate:
298
+ truncated = BatchCleaner.auto_truncate_attribute(type_, value, attribute_types)
299
+ if truncated != value and (truncated_types is None or type_ not in truncated_types):
300
+ _logger.warning(
301
+ f'feature=batch, event=attribute-truncated, attribute-type={type_}'
302
+ )
303
+ if truncated_types is not None:
304
+ truncated_types.add(type_)
305
+ else:
306
+ truncated = value
307
+
308
+ if deduplicate:
309
+ normalized = tuple(
310
+ (k, truncated if k == 'value' else attr.get(k)) for k in sorted(attr.keys())
311
+ )
312
+ if normalized in seen:
313
+ continue
314
+ seen.add(normalized)
315
+
316
+ new_attr = dict(attr)
317
+ new_attr['value'] = truncated
318
+ cleaned_attrs.append(new_attr)
319
+
320
+ item['attribute'] = cleaned_attrs
321
+ return item
322
+
323
+ @staticmethod
324
+ def merge_list_field(existing: dict, incoming: dict, field: str) -> None:
325
+ """Merge a list-of-dicts field from incoming into existing, removing duplicates.
326
+
327
+ Args:
328
+ existing: The target item (modified in place).
329
+ incoming: The source item to merge from.
330
+ field: The field name (e.g. 'tag', 'securityLabel').
331
+ """
332
+ incoming_items = incoming.get(field) or []
333
+ if not incoming_items:
334
+ return
335
+
336
+ existing_items = existing.setdefault(field, [])
337
+ seen = {frozenset(item.items()) for item in existing_items}
338
+ for item in incoming_items:
339
+ key = frozenset(item.items())
340
+ if key not in seen:
341
+ seen.add(key)
342
+ existing_items.append(item)
343
+
344
+ def deduplicate(self, incoming: dict, existing: dict) -> dict:
345
+ """Merge incoming item into existing.
346
+
347
+ For File indicators, summaries are combined via hash merging.
348
+
349
+ Args:
350
+ incoming: The new item.
351
+ existing: The previously seen item to merge into.
352
+
353
+ Returns:
354
+ The merged item (same reference as existing).
355
+ """
356
+ # merge File hash summaries
357
+ if existing.get('type') == 'File' and incoming.get('type') == 'File':
358
+ existing['summary'] = BatchCleaner.combine_file_hashes(
359
+ existing.get('summary', ''), incoming.get('summary', '')
360
+ )
361
+
362
+ # merge associated group xids
363
+ incoming_xids = incoming.get('associatedGroupXid') or []
364
+ if incoming_xids:
365
+ existing_xids = existing.get('associatedGroupXid') or []
366
+ existing['associatedGroupXid'] = list(set(existing_xids + incoming_xids))
367
+
368
+ # normalize existing tags
369
+ existing_tags = existing.get('tag') or []
370
+ if existing_tags:
371
+ self._normalize_tags(existing_tags)
372
+
373
+ # normalize incoming tags
374
+ incoming_tags = incoming.get('tag') or []
375
+ if incoming_tags:
376
+ self._normalize_tags(incoming_tags)
377
+
378
+ # merge metadata
379
+ BatchCleaner.merge_list_field(existing, incoming, 'associatedGroups')
380
+ BatchCleaner.merge_list_field(existing, incoming, 'fileOccurrence')
381
+ BatchCleaner.merge_list_field(existing, incoming, 'securityLabel')
382
+ BatchCleaner.merge_list_field(existing, incoming, 'tag')
383
+
384
+ # keep highest rating/confidence
385
+ for field in ('rating', 'confidence'):
386
+ if field in incoming:
387
+ existing[field] = max(existing.get(field, 0), incoming.get(field, 0))
388
+
389
+ # keep earliest externalDateAdded and latest externalLastModified
390
+ to_dt = DatetimeOperation.any_to_datetime
391
+ date_merge_rules = (
392
+ ('externalDateAdded', min),
393
+ ('externalDateLastModified', max),
394
+ )
395
+ for field, resolve in date_merge_rules:
396
+ if field in incoming:
397
+ existing_val = existing.get(field, incoming[field])
398
+ try:
399
+ existing[field] = resolve(existing_val, incoming[field], key=to_dt)
400
+ except Exception:
401
+ _logger.exception(f'Invalid date specified for {field}')
402
+
403
+ # combine attributes and later the cleaning pass will dedup/truncate
404
+ incoming_attrs = incoming.get('attribute') or []
405
+ if incoming_attrs:
406
+ existing['attribute'] = (existing.get('attribute') or []) + incoming_attrs
407
+
408
+ return existing
409
+
410
+ @staticmethod
411
+ def combine_file_hashes(existing_summary: str, new_summary: str) -> str:
412
+ """Combine two File indicator summaries into a single multi-hash summary.
413
+
414
+ Args:
415
+ existing_summary: The summary from the already-collected indicator.
416
+ new_summary: The summary from the incoming indicator.
417
+
418
+ Returns:
419
+ A combined summary with hashes in md5 : sha1 : sha256 order.
420
+ """
421
+ hash_type_by_len = {32: 'md5', 40: 'sha1', 64: 'sha256'}
422
+
423
+ all_hashes = [h.strip() for h in new_summary.split(' : ')]
424
+ all_hashes.extend(h.strip() for h in existing_summary.split(' : '))
425
+
426
+ hash_values: dict[str, str] = {}
427
+ for hash_value in all_hashes:
428
+ hash_type = hash_type_by_len.get(len(hash_value))
429
+ if hash_type:
430
+ hash_values[hash_type] = hash_value
431
+
432
+ return ' : '.join(hash_values[ht] for ht in ['md5', 'sha1', 'sha256'] if ht in hash_values)
433
+
434
+ @staticmethod
435
+ def _file_hash_set(summary: str) -> set[str]:
436
+ """Extract individual hashes from a File indicator summary.
437
+
438
+ Args:
439
+ summary: A File indicator summary string (e.g. 'md5 : sha1 : sha256').
440
+
441
+ Returns:
442
+ A set of individual hash strings with whitespace stripped.
443
+ """
444
+ return {h.strip() for h in summary.split(' : ') if h.strip()}
445
+
446
+ @cached_property
447
+ def naics_tags(self) -> NAICSTags:
448
+ """NAICS Tags"""
449
+ return NAICSTags()