psengine 2.11.0__tar.gz → 2.12.0__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 (194) hide show
  1. {psengine-2.11.0 → psengine-2.12.0}/PKG-INFO +5 -1
  2. {psengine-2.11.0 → psengine-2.12.0}/README.md +4 -0
  3. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/note_mgr.py +2 -2
  4. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/soar_mgr.py +3 -9
  5. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_lists/entity_list.py +5 -3
  6. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_match/entity_match_mgr.py +2 -2
  7. {psengine-2.11.0 → psengine-2.12.0}/psengine/helpers/__init__.py +1 -0
  8. psengine-2.12.0/psengine/helpers/validation.py +111 -0
  9. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/identity_mgr.py +7 -7
  10. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_history/risk_history_mgr.py +2 -2
  11. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_rules/risk_rule_mgr.py +2 -2
  12. {psengine-2.11.0 → psengine-2.12.0}/psengine/threat_maps/threat_map_mgr.py +4 -4
  13. {psengine-2.11.0 → psengine-2.12.0}/pyproject.toml +1 -1
  14. {psengine-2.11.0 → psengine-2.12.0}/pyproject.toml.orig +1 -1
  15. {psengine-2.11.0 → psengine-2.12.0}/psengine/__init__.py +0 -0
  16. {psengine-2.11.0 → psengine-2.12.0}/psengine/_sdk_id.py +0 -0
  17. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/__init__.py +0 -0
  18. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/constants.py +0 -0
  19. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/errors.py +0 -0
  20. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/helpers.py +0 -0
  21. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/markdown.py +0 -0
  22. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/models.py +0 -0
  23. {psengine-2.11.0 → psengine-2.12.0}/psengine/analyst_notes/note.py +0 -0
  24. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/__init__.py +0 -0
  25. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/asi.py +0 -0
  26. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/asi_mgr.py +0 -0
  27. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/client.py +0 -0
  28. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/constants.py +0 -0
  29. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/errors.py +0 -0
  30. {psengine-2.11.0 → psengine-2.12.0}/psengine/asi/models.py +0 -0
  31. {psengine-2.11.0 → psengine-2.12.0}/psengine/base_http_client.py +0 -0
  32. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/__init__.py +0 -0
  33. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/classic_alert.py +0 -0
  34. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  35. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/constants.py +0 -0
  36. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/errors.py +0 -0
  37. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/helpers.py +0 -0
  38. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  39. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
  40. {psengine-2.11.0 → psengine-2.12.0}/psengine/classic_alerts/models.py +0 -0
  41. {psengine-2.11.0 → psengine-2.12.0}/psengine/collective_insights/__init__.py +0 -0
  42. {psengine-2.11.0 → psengine-2.12.0}/psengine/collective_insights/collective_insights.py +0 -0
  43. {psengine-2.11.0 → psengine-2.12.0}/psengine/collective_insights/constants.py +0 -0
  44. {psengine-2.11.0 → psengine-2.12.0}/psengine/collective_insights/errors.py +0 -0
  45. {psengine-2.11.0 → psengine-2.12.0}/psengine/collective_insights/insight.py +0 -0
  46. {psengine-2.11.0 → psengine-2.12.0}/psengine/collective_insights/models.py +0 -0
  47. {psengine-2.11.0 → psengine-2.12.0}/psengine/common_models.py +0 -0
  48. {psengine-2.11.0 → psengine-2.12.0}/psengine/config/__init__.py +0 -0
  49. {psengine-2.11.0 → psengine-2.12.0}/psengine/config/config.py +0 -0
  50. {psengine-2.11.0 → psengine-2.12.0}/psengine/config/errors.py +0 -0
  51. {psengine-2.11.0 → psengine-2.12.0}/psengine/constants.py +0 -0
  52. {psengine-2.11.0 → psengine-2.12.0}/psengine/detection/__init__.py +0 -0
  53. {psengine-2.11.0 → psengine-2.12.0}/psengine/detection/detection_mgr.py +0 -0
  54. {psengine-2.11.0 → psengine-2.12.0}/psengine/detection/detection_rule.py +0 -0
  55. {psengine-2.11.0 → psengine-2.12.0}/psengine/detection/errors.py +0 -0
  56. {psengine-2.11.0 → psengine-2.12.0}/psengine/detection/helpers.py +0 -0
  57. {psengine-2.11.0 → psengine-2.12.0}/psengine/detection/models.py +0 -0
  58. {psengine-2.11.0 → psengine-2.12.0}/psengine/endpoints.py +0 -0
  59. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/__init__.py +0 -0
  60. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/constants.py +0 -0
  61. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/errors.py +0 -0
  62. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/lookup.py +0 -0
  63. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/lookup_mgr.py +0 -0
  64. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/models/__init__.py +0 -0
  65. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  66. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/models/lookup.py +0 -0
  67. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/models/soar.py +0 -0
  68. {psengine-2.11.0 → psengine-2.12.0}/psengine/enrich/soar.py +0 -0
  69. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_lists/__init__.py +0 -0
  70. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_lists/constants.py +0 -0
  71. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
  72. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_lists/errors.py +0 -0
  73. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_lists/models.py +0 -0
  74. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_match/__init__.py +0 -0
  75. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_match/entity_match.py +0 -0
  76. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_match/errors.py +0 -0
  77. {psengine-2.11.0 → psengine-2.12.0}/psengine/entity_match/models.py +0 -0
  78. {psengine-2.11.0 → psengine-2.12.0}/psengine/errors.py +0 -0
  79. {psengine-2.11.0 → psengine-2.12.0}/psengine/fusion/__init__.py +0 -0
  80. {psengine-2.11.0 → psengine-2.12.0}/psengine/fusion/errors.py +0 -0
  81. {psengine-2.11.0 → psengine-2.12.0}/psengine/fusion/fusion_mgr.py +0 -0
  82. {psengine-2.11.0 → psengine-2.12.0}/psengine/fusion/models.py +0 -0
  83. {psengine-2.11.0 → psengine-2.12.0}/psengine/helpers/helpers.py +0 -0
  84. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/__init__.py +0 -0
  85. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/constants.py +0 -0
  86. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/errors.py +0 -0
  87. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/identity.py +0 -0
  88. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/models/__init__.py +0 -0
  89. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/models/common_models.py +0 -0
  90. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/models/detections.py +0 -0
  91. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/models/incident_report.py +0 -0
  92. {psengine-2.11.0 → psengine-2.12.0}/psengine/identity/models/lookup.py +0 -0
  93. {psengine-2.11.0 → psengine-2.12.0}/psengine/links/__init__.py +0 -0
  94. {psengine-2.11.0 → psengine-2.12.0}/psengine/links/errors.py +0 -0
  95. {psengine-2.11.0 → psengine-2.12.0}/psengine/links/links.py +0 -0
  96. {psengine-2.11.0 → psengine-2.12.0}/psengine/links/links_mgr.py +0 -0
  97. {psengine-2.11.0 → psengine-2.12.0}/psengine/links/models.py +0 -0
  98. {psengine-2.11.0 → psengine-2.12.0}/psengine/logger/__init__.py +0 -0
  99. {psengine-2.11.0 → psengine-2.12.0}/psengine/logger/constants.py +0 -0
  100. {psengine-2.11.0 → psengine-2.12.0}/psengine/logger/errors.py +0 -0
  101. {psengine-2.11.0 → psengine-2.12.0}/psengine/logger/rf_logger.py +0 -0
  102. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/__init__.py +0 -0
  103. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/auto_sigma_mgr.py +0 -0
  104. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/auto_yara_mgr.py +0 -0
  105. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/constants.py +0 -0
  106. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/errors.py +0 -0
  107. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/helpers.py +0 -0
  108. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/malware_intel.py +0 -0
  109. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/malware_intel_mgr.py +0 -0
  110. {psengine-2.11.0 → psengine-2.12.0}/psengine/malware_intel/models.py +0 -0
  111. {psengine-2.11.0 → psengine-2.12.0}/psengine/markdown/__init__.py +0 -0
  112. {psengine-2.11.0 → psengine-2.12.0}/psengine/markdown/markdown.py +0 -0
  113. {psengine-2.11.0 → psengine-2.12.0}/psengine/markdown/markdown_strings.py +0 -0
  114. {psengine-2.11.0 → psengine-2.12.0}/psengine/markdown/models.py +0 -0
  115. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/__init__.py +0 -0
  116. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/constants.py +0 -0
  117. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/errors.py +0 -0
  118. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/helpers.py +0 -0
  119. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/mappings.py +0 -0
  120. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  121. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  122. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  123. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_compromised_bank_checks.py +0 -0
  124. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  125. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  126. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  127. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  128. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_malicious_sites.py +0 -0
  129. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  130. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  131. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  132. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  133. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/__init__.py +0 -0
  134. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/code_repo.py +0 -0
  135. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/common.py +0 -0
  136. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/domain_abuse.py +0 -0
  137. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/generic.py +0 -0
  138. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/geopolitics.py +0 -0
  139. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/malicious_sites.py +0 -0
  140. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/panel_log_v2.py +0 -0
  141. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/tpr.py +0 -0
  142. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_log/vulnerability.py +0 -0
  143. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  144. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  145. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_compromised_bank_checks.py +0 -0
  146. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  147. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  148. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  149. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  150. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_malicious_sites.py +0 -0
  151. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  152. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  153. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  154. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/pa_category.py +0 -0
  155. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
  156. {psengine-2.11.0 → psengine-2.12.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
  157. {psengine-2.11.0 → psengine-2.12.0}/psengine/rf_client.py +0 -0
  158. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_history/__init__.py +0 -0
  159. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_history/errors.py +0 -0
  160. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_history/models.py +0 -0
  161. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_rules/__init__.py +0 -0
  162. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_rules/errors.py +0 -0
  163. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_rules/models.py +0 -0
  164. {psengine-2.11.0 → psengine-2.12.0}/psengine/risk_rules/risk_rule.py +0 -0
  165. {psengine-2.11.0 → psengine-2.12.0}/psengine/risklists/__init__.py +0 -0
  166. {psengine-2.11.0 → psengine-2.12.0}/psengine/risklists/constants.py +0 -0
  167. {psengine-2.11.0 → psengine-2.12.0}/psengine/risklists/errors.py +0 -0
  168. {psengine-2.11.0 → psengine-2.12.0}/psengine/risklists/models.py +0 -0
  169. {psengine-2.11.0 → psengine-2.12.0}/psengine/risklists/risklist_mgr.py +0 -0
  170. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/__init__.py +0 -0
  171. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/client.py +0 -0
  172. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/constants.py +0 -0
  173. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/errors.py +0 -0
  174. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/models/__init__.py +0 -0
  175. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/models/analysis.py +0 -0
  176. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/models/behavioral_report.py +0 -0
  177. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/models/overview_report.py +0 -0
  178. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/models/static_report.py +0 -0
  179. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/sandbox.py +0 -0
  180. {psengine-2.11.0 → psengine-2.12.0}/psengine/sandbox/sandbox_mgr.py +0 -0
  181. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/__init__.py +0 -0
  182. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/base_stix_entity.py +0 -0
  183. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/complex_entity.py +0 -0
  184. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/constants.py +0 -0
  185. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/enriched_indicator.py +0 -0
  186. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/errors.py +0 -0
  187. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/helpers.py +0 -0
  188. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/rf_bundle.py +0 -0
  189. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/simple_entity.py +0 -0
  190. {psengine-2.11.0 → psengine-2.12.0}/psengine/stix2/util.py +0 -0
  191. {psengine-2.11.0 → psengine-2.12.0}/psengine/threat_maps/__init__.py +0 -0
  192. {psengine-2.11.0 → psengine-2.12.0}/psengine/threat_maps/errors.py +0 -0
  193. {psengine-2.11.0 → psengine-2.12.0}/psengine/threat_maps/models.py +0 -0
  194. {psengine-2.11.0 → psengine-2.12.0}/psengine/threat_maps/threat_map.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.11.0
3
+ Version: 2.12.0
4
4
  Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
5
5
  Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
6
6
  Author: Moise Medici, Patrick Kinsella, Ernest Bartosevic
@@ -93,3 +93,7 @@ PSEngine has been made public from our internal version 2.0.4. Previous versions
93
93
 
94
94
  The documentation arrived at version 2.1.1. Older versions are not explicitly documented and changes can be found in the [Release History](./CHANGELOG.md) section.
95
95
 
96
+ ## Contributing
97
+
98
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, code standards, testing conventions, and the CI workflow.
99
+
@@ -60,3 +60,7 @@ PSEngine has been made public from our internal version 2.0.4. Previous versions
60
60
 
61
61
  The documentation arrived at version 2.1.1. Older versions are not explicitly documented and changes can be found in the [Release History](./CHANGELOG.md) section.
62
62
 
63
+ ## Contributing
64
+
65
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, code standards, testing conventions, and the CI workflow.
66
+
@@ -28,7 +28,7 @@ from ..endpoints import (
28
28
  EP_ANALYST_NOTE_PUBLISH,
29
29
  EP_ANALYST_NOTE_SEARCH,
30
30
  )
31
- from ..helpers import debug_call
31
+ from ..helpers import debug_call, validate_list
32
32
  from ..helpers.helpers import connection_exceptions
33
33
  from ..rf_client import RFClient
34
34
  from .constants import NOTES_PER_PAGE
@@ -358,4 +358,4 @@ class AnalystNoteMgr:
358
358
  max_results=max_results,
359
359
  )
360
360
 
361
- return [AnalystNote.model_validate(d) for d in response]
361
+ return validate_list(AnalystNote, response, id_path='id', log=self.log)
@@ -19,7 +19,7 @@ from pydantic import validate_call
19
19
  from typing_extensions import Doc
20
20
 
21
21
  from ..endpoints import EP_SOAR_ENRICHMENT
22
- from ..helpers import MultiThreadingHelper, connection_exceptions, debug_call
22
+ from ..helpers import MultiThreadingHelper, connection_exceptions, debug_call, validate_list
23
23
  from ..rf_client import RFClient
24
24
  from .constants import SOAR_POST_ROWS
25
25
  from .errors import EnrichmentSoarError
@@ -158,11 +158,5 @@ class SoarMgr:
158
158
  res = self.rf_client.request('post', EP_SOAR_ENRICHMENT, data=data.json()).json()['data'][
159
159
  'results'
160
160
  ]
161
- result = []
162
-
163
- for d in res:
164
- content = SOAREnrichedEntity.model_validate(d)
165
- entity = content.entity.name
166
- result.append(SOAREnrichOut(entity=entity, is_enriched=True, content=content))
167
-
168
- return result
161
+ contents = validate_list(SOAREnrichedEntity, res, id_path='entity.name', log=self.log)
162
+ return [SOAREnrichOut(entity=c.entity.name, is_enriched=True, content=c) for c in contents]
@@ -24,7 +24,7 @@ from ..common_models import IdNameType, RFBaseModel
24
24
  from ..constants import TIMESTAMP_STR
25
25
  from ..endpoints import EP_LIST, EP_LIST_ENTITIES_WITH_TAGS, EP_LIST_ENTITY_TAGS
26
26
  from ..entity_match import EntityMatchMgr, MatchApiError
27
- from ..helpers import debug_call
27
+ from ..helpers import debug_call, validate_list
28
28
  from ..helpers.helpers import connection_exceptions
29
29
  from ..rf_client import RFClient
30
30
  from .constants import ADD_OP, ERROR_NAME, IS_READY_INCREMENT, REMOVE_OP, UNCHANGED_NAME
@@ -286,7 +286,7 @@ class EntityList(ListInfoOut):
286
286
  """
287
287
  url = EP_LIST + '/' + self.id_ + '/entities'
288
288
  response = self.rf_client.request('get', url)
289
- return [ListEntity.model_validate(entity) for entity in response.json()]
289
+ return validate_list(ListEntity, response.json(), id_path='entity.name', log=self.log)
290
290
 
291
291
  @debug_call
292
292
  @connection_exceptions(ignore_status_code=[], exception_to_raise=ListApiError)
@@ -319,7 +319,9 @@ class EntityList(ListInfoOut):
319
319
  """
320
320
  url = EP_LIST_ENTITIES_WITH_TAGS.format(self.id_)
321
321
  response = self.rf_client.request('get', url)
322
- return [ListEntityWithTags.model_validate(entity) for entity in response.json()]
322
+ return validate_list(
323
+ ListEntityWithTags, response.json(), id_path='entity.name', log=self.log
324
+ )
323
325
 
324
326
  @debug_call
325
327
  @validate_call
@@ -21,7 +21,7 @@ from typing_extensions import Doc
21
21
  from ..common_models import IdNameType
22
22
  from ..constants import DEFAULT_LIMIT, DEFAULT_MAX_WORKERS
23
23
  from ..endpoints import EP_ENTITY_LOOKUP, EP_ENTITY_MATCH
24
- from ..helpers import MultiThreadingHelper, connection_exceptions, debug_call
24
+ from ..helpers import MultiThreadingHelper, connection_exceptions, debug_call, validate_list
25
25
  from ..rf_client import RFClient
26
26
  from .entity_match import EntityLookup, EntityMatchIn, ResolvedEntity
27
27
  from .errors import MatchApiError
@@ -63,7 +63,7 @@ class EntityMatchMgr:
63
63
 
64
64
  request_body = EntityMatchIn(name=entity_name, type=entity_type, limit=limit)
65
65
  response = self.rf_client.request('post', EP_ENTITY_MATCH, data=request_body.json())
66
- response = [IdNameType.model_validate(d) for d in response.json()]
66
+ response = validate_list(IdNameType, response.json(), id_path='name', log=self.log)
67
67
  return (
68
68
  list({ResolvedEntity(entity=d.name, is_found=bool(d.id_), content=d) for d in response})
69
69
  if response
@@ -22,3 +22,4 @@ from .helpers import (
22
22
  debug_call,
23
23
  dump_models,
24
24
  )
25
+ from .validation import validate_list
@@ -0,0 +1,111 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ import logging
15
+ from collections.abc import Iterable
16
+ from typing import Annotated, Any, TypeVar
17
+
18
+ from pydantic import BaseModel, TypeAdapter, ValidationError
19
+ from typing_extensions import Doc
20
+
21
+ LOG = logging.getLogger(__name__)
22
+
23
+ T = TypeVar('T', bound=BaseModel)
24
+
25
+
26
+ def validate_list(
27
+ model_cls: Annotated[type[T], Doc('Pydantic model each item should validate against.')],
28
+ items: Annotated[
29
+ Iterable[Any], Doc('Raw items to validate, typically dicts from an API response.')
30
+ ],
31
+ id_path: Annotated[
32
+ str | None,
33
+ Doc(
34
+ 'Dotted path into each raw item used to build a friendly identifier for log lines '
35
+ "and exception notes, e.g. `'entity.name'`. If unresolvable for a given item, only "
36
+ 'the index is reported.'
37
+ ),
38
+ ] = None,
39
+ log: Annotated[
40
+ logging.Logger | None,
41
+ Doc("Logger for the warnings. Defaults to this module's logger."),
42
+ ] = None,
43
+ ) -> Annotated[list[T], Doc('The list of validated `model_cls` instances.')]:
44
+ """Validate a list of dicts against `model_cls`.
45
+
46
+ Uses pydantic's `TypeAdapter` so a single validation pass collects all per-item errors with an
47
+ index in their `loc`.
48
+
49
+ Before re-raising, each failing item is (a) logged individually as a warning and (b) on
50
+ Python 3.11+ attached to the exception via `add_note`, so the entity identifier shows up in
51
+ the traceback too. On Python 3.10 the note step is skipped since `BaseException.add_note`
52
+ is not available.
53
+
54
+ If `id_path` is provided, a human-readable identifier is extracted from the raw dict
55
+ (e.g. `entity.name`); otherwise only the index is reported.
56
+
57
+ Raises:
58
+ pydantic.ValidationError: If any item fails validation. Unchanged
59
+ from `[model_cls.model_validate(x) for x in items]`.
60
+ """
61
+ if not isinstance(items, list):
62
+ items = list(items)
63
+ try:
64
+ return TypeAdapter(list[model_cls]).validate_python(items)
65
+ except ValidationError as e:
66
+ for msg in _format_failures(e, items, id_path, model_cls):
67
+ (log or LOG).warning(msg)
68
+ if hasattr(e, 'add_note'):
69
+ e.add_note(msg)
70
+ raise
71
+
72
+
73
+ def _format_failures(
74
+ exc: ValidationError,
75
+ items: list[Any],
76
+ id_path: str | None,
77
+ model_cls: type[BaseModel],
78
+ ) -> list[str]:
79
+ """Build one message per failing item, keyed by index and optional id."""
80
+ seen: set[int] = set()
81
+ messages: list[str] = []
82
+ for err in exc.errors():
83
+ loc = err.get('loc') or ()
84
+ if not loc or not isinstance(loc[0], int):
85
+ continue
86
+ idx = loc[0]
87
+ if idx in seen:
88
+ continue
89
+ seen.add(idx)
90
+
91
+ raw = items[idx] if 0 <= idx < len(items) else None
92
+ identifier = _resolve_id_path(raw, id_path) if id_path else None
93
+ id_hint = f' ({id_path}={identifier})' if identifier is not None else ''
94
+ field_path = '.'.join(str(p) for p in loc[1:]) or '<root>'
95
+ messages.append(
96
+ f'{model_cls.__name__} validation failed at index {idx}{id_hint}: '
97
+ f"field '{field_path}' {err.get('msg', '')}"
98
+ )
99
+ return messages
100
+
101
+
102
+ def _resolve_id_path(item: Any, id_path: str) -> Any:
103
+ """Walk `id_path` (dot-separated) into `item`. Returns None on miss."""
104
+ value: Any = item
105
+ for part in id_path.split('.'):
106
+ if not isinstance(value, dict):
107
+ return None
108
+ value = value.get(part)
109
+ if value is None:
110
+ return None
111
+ return value
@@ -29,7 +29,7 @@ from ..endpoints import (
29
29
  EP_IDENTITY_IP_LOOKUP,
30
30
  EP_IDENTITY_PASSWORD_LOOKUP,
31
31
  )
32
- from ..helpers import TimeHelpers, connection_exceptions, debug_call
32
+ from ..helpers import TimeHelpers, connection_exceptions, debug_call, validate_list
33
33
  from ..rf_client import RFClient
34
34
  from .constants import DETECTIONS_PER_PAGE, MAXIMUM_IDENTITIES
35
35
  from .errors import (
@@ -252,7 +252,7 @@ class IdentityMgr:
252
252
  results_path='identities',
253
253
  max_results=max_results or DEFAULT_LIMIT,
254
254
  )
255
- resp = [LeakedIdentity.model_validate(identity) for identity in resp]
255
+ resp = validate_list(LeakedIdentity, resp, log=self.log)
256
256
  self.log.info(f'Returned {len(resp)} identities')
257
257
  return resp
258
258
 
@@ -321,7 +321,7 @@ class IdentityMgr:
321
321
  resp = self.rf_client.request('post', url=EP_IDENTITY_PASSWORD_LOOKUP, data=data).json()[
322
322
  'results'
323
323
  ]
324
- resp = [PasswordLookup.model_validate(v) for v in resp]
324
+ resp = validate_list(PasswordLookup, resp, id_path='password.hash_prefix', log=self.log)
325
325
  self.log.info(f'Returned {len(resp)} passwords')
326
326
  return resp
327
327
 
@@ -436,7 +436,7 @@ class IdentityMgr:
436
436
  max_results=max_results or DEFAULT_LIMIT,
437
437
  results_path='identities',
438
438
  )
439
- resp = [LeakedIdentity.model_validate(identity) for identity in resp]
439
+ resp = validate_list(LeakedIdentity, resp, log=self.log)
440
440
  self.log.info(f'Returned {len(resp)} identities')
441
441
  return resp
442
442
 
@@ -561,7 +561,7 @@ class IdentityMgr:
561
561
  max_results=max_results or DEFAULT_LIMIT,
562
562
  results_path='identities',
563
563
  )
564
- resp = [LeakedIdentity.model_validate(identity) for identity in resp]
564
+ resp = validate_list(LeakedIdentity, resp, log=self.log)
565
565
  self.log.info(f'Returned {len(resp)} identities')
566
566
  return resp
567
567
 
@@ -663,7 +663,7 @@ class IdentityMgr:
663
663
  max_results=max_results or DEFAULT_LIMIT,
664
664
  results_path='identities',
665
665
  )
666
- resp = [CredentialSearch.model_validate(d) for d in resp]
666
+ resp = validate_list(CredentialSearch, resp, id_path='login', log=self.log)
667
667
  self.log.info(f'Returned {len(resp)} credentials')
668
668
  return resp
669
669
 
@@ -707,7 +707,7 @@ class IdentityMgr:
707
707
  resp = self.rf_client.request('post', url=EP_IDENTITY_DUMP_SEARCH, data=payload).json()[
708
708
  'dumps'
709
709
  ]
710
- resp = [DumpSearchOut.model_validate(d) for d in resp]
710
+ resp = validate_list(DumpSearchOut, resp, id_path='name', log=self.log)
711
711
  self.log.info(f'Returned {len(resp)} dump results')
712
712
  return resp
713
713
 
@@ -18,7 +18,7 @@ from pydantic import validate_call
18
18
  from typing_extensions import Doc
19
19
 
20
20
  from ..endpoints import EP_RISK_HISTORY
21
- from ..helpers import debug_call
21
+ from ..helpers import debug_call, validate_list
22
22
  from ..helpers.helpers import connection_exceptions
23
23
  from ..rf_client import RFClient
24
24
  from .errors import RiskHistoryError
@@ -57,4 +57,4 @@ class RiskHistoryMgr:
57
57
  data = RiskHistoryIn.model_validate({'entities': entities, 'from': from_, 'to': to})
58
58
  attrs = self.rf_client.request('post', EP_RISK_HISTORY, data=data.json()).json()['data']
59
59
 
60
- return [RiskHistory.model_validate(attr) for attr in attrs]
60
+ return validate_list(RiskHistory, attrs, id_path='entity.id', log=self.log)
@@ -18,7 +18,7 @@ from pydantic import validate_call
18
18
  from typing_extensions import Doc
19
19
 
20
20
  from ..endpoints import EP_RISK_RULES
21
- from ..helpers import debug_call
21
+ from ..helpers import debug_call, validate_list
22
22
  from ..helpers.helpers import connection_exceptions
23
23
  from ..rf_client import RFClient
24
24
  from .errors import RiskRuleFetchError
@@ -60,4 +60,4 @@ class RiskRuleMgr:
60
60
  self.log.info(f'Fetching risk rules for entity type: {entity_type.value}')
61
61
  response = self.rf_client.request('get', url).json()
62
62
  results = response['data']['results']
63
- return [RiskRule.model_validate(r) for r in results]
63
+ return validate_list(RiskRule, results, id_path='name', log=self.log)
@@ -25,7 +25,7 @@ from ..endpoints import (
25
25
  EP_THREAT_MAP_ORG,
26
26
  EP_THREAT_MAPS_LIST,
27
27
  )
28
- from ..helpers import debug_call
28
+ from ..helpers import debug_call, validate_list
29
29
  from ..helpers.helpers import connection_exceptions
30
30
  from ..rf_client import RFClient
31
31
  from .errors import (
@@ -73,7 +73,7 @@ class ThreatMapMgr:
73
73
  ThreatMapInfoError: If connection error occurs.
74
74
  """
75
75
  maps_response = self.rf_client.request(method='get', url=EP_THREAT_MAPS_LIST).json()['data']
76
- return [ThreatMapInfo.model_validate(entry) for entry in maps_response]
76
+ return validate_list(ThreatMapInfo, maps_response, id_path='name', log=self.log)
77
77
 
78
78
  @debug_call
79
79
  @validate_call
@@ -94,7 +94,7 @@ class ThreatMapMgr:
94
94
  map_type = ThreatMapType(map_type)
95
95
  url = EP_CATEGORIES.format(map_type.category_slug)
96
96
  cat_response = self.rf_client.request(method='get', url=url).json()['data']
97
- return [EntityCategory.model_validate(ent) for ent in cat_response]
97
+ return validate_list(EntityCategory, cat_response, id_path='id', log=self.log)
98
98
 
99
99
  @debug_call
100
100
  @validate_call
@@ -134,7 +134,7 @@ class ThreatMapMgr:
134
134
  offset_key='offset',
135
135
  max_results=max_results or DEFAULT_LIMIT,
136
136
  )
137
- return [ThreatActorProfile.model_validate(ta) for ta in search_response]
137
+ return validate_list(ThreatActorProfile, search_response, id_path='id', log=self.log)
138
138
 
139
139
  @debug_call
140
140
  @validate_call
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "psengine"
3
- version = "2.11.0"
3
+ version = "2.12.0"
4
4
  readme = "README.md"
5
5
  license = "MIT"
6
6
  requires-python = ">=3.10, <3.15"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "psengine"
3
- version = "2.11.0"
3
+ version = "2.12.0"
4
4
  readme = "README.md"
5
5
  license = "MIT"
6
6
  requires-python = ">=3.10, <3.15"
File without changes
File without changes
File without changes