psengine 2.1.0__tar.gz → 2.2.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 (144) hide show
  1. psengine-2.2.0/PKG-INFO +103 -0
  2. psengine-2.2.0/README.md +49 -0
  3. {psengine-2.1.0 → psengine-2.2.0}/psengine/_version.py +1 -1
  4. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/helpers.py +13 -22
  5. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/models.py +8 -4
  6. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/note.py +43 -46
  7. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/note_mgr.py +114 -157
  8. {psengine-2.1.0 → psengine-2.2.0}/psengine/base_http_client.py +48 -71
  9. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/classic_alert.py +99 -114
  10. psengine-2.2.0/psengine/classic_alerts/classic_alert_mgr.py +505 -0
  11. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/helpers.py +25 -28
  12. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/markdown/markdown.py +15 -12
  13. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/models.py +1 -1
  14. {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/collective_insights.py +35 -42
  15. {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/insight.py +28 -19
  16. {psengine-2.1.0 → psengine-2.2.0}/psengine/common_models.py +29 -22
  17. {psengine-2.1.0 → psengine-2.2.0}/psengine/config/config.py +84 -92
  18. {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/detection_mgr.py +47 -48
  19. {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/detection_rule.py +21 -20
  20. {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/helpers.py +15 -10
  21. {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/models.py +8 -5
  22. {psengine-2.1.0 → psengine-2.2.0}/psengine/endpoints.py +1 -1
  23. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/constants.py +14 -17
  24. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/lookup.py +68 -56
  25. psengine-2.2.0/psengine/enrich/lookup_mgr.py +316 -0
  26. {psengine-2.1.0/psengine/playbook_alerts/markdown → psengine-2.2.0/psengine/enrich/models}/__init__.py +1 -0
  27. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/models/lookup.py +1 -4
  28. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/models/soar.py +45 -45
  29. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/soar.py +21 -19
  30. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/soar_mgr.py +50 -56
  31. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/entity_list.py +119 -109
  32. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/entity_list_mgr.py +37 -47
  33. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/models.py +8 -8
  34. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/entity_match.py +24 -21
  35. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/entity_match_mgr.py +76 -93
  36. {psengine-2.1.0 → psengine-2.2.0}/psengine/helpers/helpers.py +144 -148
  37. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/identity.py +101 -105
  38. psengine-2.2.0/psengine/identity/identity_mgr.py +836 -0
  39. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/common_models.py +1 -1
  40. {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/rf_logger.py +20 -10
  41. {psengine-2.1.0 → psengine-2.2.0}/psengine/markdown/markdown.py +48 -16
  42. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/__init__.py +1 -0
  43. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/helpers.py +13 -15
  44. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +2 -3
  45. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/panel_log.py +7 -7
  46. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/pa_category.py +6 -7
  47. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/playbook_alert_mgr.py +192 -180
  48. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/playbook_alerts.py +121 -110
  49. psengine-2.2.0/psengine/py.typed +0 -0
  50. {psengine-2.1.0 → psengine-2.2.0}/psengine/rf_client.py +93 -124
  51. {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/models.py +14 -3
  52. {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/risklist_mgr.py +42 -48
  53. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/base_stix_entity.py +10 -13
  54. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/complex_entity.py +56 -76
  55. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/enriched_indicator.py +19 -32
  56. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/helpers.py +20 -16
  57. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/rf_bundle.py +25 -41
  58. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/simple_entity.py +17 -17
  59. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/util.py +8 -13
  60. psengine-2.2.0/psengine.egg-info/PKG-INFO +103 -0
  61. {psengine-2.1.0 → psengine-2.2.0}/psengine.egg-info/SOURCES.txt +2 -1
  62. psengine-2.2.0/psengine.egg-info/requires.txt +32 -0
  63. {psengine-2.1.0 → psengine-2.2.0}/pyproject.toml +37 -33
  64. psengine-2.1.0/PKG-INFO +0 -189
  65. psengine-2.1.0/README.rst +0 -146
  66. psengine-2.1.0/psengine/classic_alerts/classic_alert_mgr.py +0 -504
  67. psengine-2.1.0/psengine/enrich/lookup_mgr.py +0 -341
  68. psengine-2.1.0/psengine/identity/identity_mgr.py +0 -895
  69. psengine-2.1.0/psengine.egg-info/PKG-INFO +0 -189
  70. psengine-2.1.0/psengine.egg-info/requires.txt +0 -30
  71. {psengine-2.1.0 → psengine-2.2.0}/LICENSE +0 -0
  72. {psengine-2.1.0 → psengine-2.2.0}/psengine/__init__.py +0 -0
  73. {psengine-2.1.0 → psengine-2.2.0}/psengine/_sdk_id.py +0 -0
  74. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/__init__.py +0 -0
  75. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/constants.py +0 -0
  76. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/errors.py +0 -0
  77. {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/markdown.py +0 -0
  78. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/__init__.py +0 -0
  79. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/constants.py +0 -0
  80. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/errors.py +0 -0
  81. {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  82. {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/__init__.py +0 -0
  83. {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/constants.py +0 -0
  84. {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/errors.py +0 -0
  85. {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/models.py +0 -0
  86. {psengine-2.1.0 → psengine-2.2.0}/psengine/config/__init__.py +0 -0
  87. {psengine-2.1.0 → psengine-2.2.0}/psengine/config/errors.py +0 -0
  88. {psengine-2.1.0 → psengine-2.2.0}/psengine/constants.py +0 -0
  89. {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/__init__.py +0 -0
  90. {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/errors.py +0 -0
  91. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/__init__.py +0 -0
  92. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/errors.py +0 -0
  93. {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  94. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/__init__.py +0 -0
  95. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/constants.py +0 -0
  96. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/errors.py +0 -0
  97. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/__init__.py +0 -0
  98. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/errors.py +0 -0
  99. {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/models.py +0 -0
  100. {psengine-2.1.0 → psengine-2.2.0}/psengine/errors.py +0 -0
  101. {psengine-2.1.0 → psengine-2.2.0}/psengine/helpers/__init__.py +0 -0
  102. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/__init__.py +0 -0
  103. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/constants.py +0 -0
  104. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/errors.py +0 -0
  105. {psengine-2.1.0/psengine/enrich → psengine-2.2.0/psengine/identity}/models/__init__.py +0 -0
  106. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/detections.py +0 -0
  107. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/incident_report.py +0 -0
  108. {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/lookup.py +0 -0
  109. {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/__init__.py +0 -0
  110. {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/constants.py +0 -0
  111. {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/errors.py +0 -0
  112. {psengine-2.1.0 → psengine-2.2.0}/psengine/markdown/__init__.py +0 -0
  113. {psengine-2.1.0 → psengine-2.2.0}/psengine/markdown/models.py +0 -0
  114. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/constants.py +0 -0
  115. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/errors.py +0 -0
  116. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/mappings.py +0 -0
  117. {psengine-2.1.0/psengine/identity/models → psengine-2.2.0/psengine/playbook_alerts/markdown}/__init__.py +0 -0
  118. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  119. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  120. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  121. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  122. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  123. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  124. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  125. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  126. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  127. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  128. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  129. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  130. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  131. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  132. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  133. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  134. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  135. {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  136. {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/__init__.py +0 -0
  137. {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/constants.py +0 -0
  138. {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/errors.py +0 -0
  139. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/__init__.py +0 -0
  140. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/constants.py +0 -0
  141. {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/errors.py +0 -0
  142. {psengine-2.1.0 → psengine-2.2.0}/psengine.egg-info/dependency_links.txt +0 -0
  143. {psengine-2.1.0 → psengine-2.2.0}/psengine.egg-info/top_level.txt +0 -0
  144. {psengine-2.1.0 → psengine-2.2.0}/setup.cfg +0 -0
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: psengine
3
+ Version: 2.2.0
4
+ Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
5
+ Author-email: Moise Medici <moise.medici@recordedfuture.com>, Patrick Kinsella <patrick.kinsella@recordedfuture.com>, Ernest Bartosevic <ernest.bartosevic@recordedfuture.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/RecordedFuture-ProfessionalServices/psengine
8
+ Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/CHANGELOG/
9
+ Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Security
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: <3.14,>=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: requests>=2.27.1
24
+ Requires-Dist: jsonpath_ng<=1.6.1,>=1.5.3
25
+ Requires-Dist: stix2~=3.0.1
26
+ Requires-Dist: python-dateutil>=2.7.0
27
+ Requires-Dist: more-itertools<=10.2.0,>=9.0.0
28
+ Requires-Dist: pydantic<3.0.0,>=2.7
29
+ Requires-Dist: pydantic-settings[toml]<2.11.0,>=2.5.2
30
+ Requires-Dist: markdown-strings==3.4.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest==8.3.4; extra == "dev"
33
+ Requires-Dist: pytest-cov==6.0.0; extra == "dev"
34
+ Requires-Dist: pytest-mock==3.14.0; extra == "dev"
35
+ Requires-Dist: pytest-md==0.2.0; extra == "dev"
36
+ Requires-Dist: pytest-random-order==1.1.1; extra == "dev"
37
+ Requires-Dist: pytest-httpdbg==0.9.0; extra == "dev"
38
+ Requires-Dist: ruff~=0.11.0; extra == "dev"
39
+ Requires-Dist: mimesis>=12.1.0; extra == "dev"
40
+ Requires-Dist: build==1.3.0; extra == "dev"
41
+ Requires-Dist: wheel==0.45.1; extra == "dev"
42
+ Requires-Dist: setuptools==80.9.0; extra == "dev"
43
+ Provides-Extra: docs
44
+ Requires-Dist: ruff~=0.11.0; extra == "docs"
45
+ Requires-Dist: mike~=2.1.3; extra == "docs"
46
+ Requires-Dist: mkdocs~=1.6.1; extra == "docs"
47
+ Requires-Dist: mkdocs-material~=9.6.18; extra == "docs"
48
+ Requires-Dist: mkdocstrings[python]>=0.18; extra == "docs"
49
+ Requires-Dist: griffe-typingdoc~=0.2.8; extra == "docs"
50
+ Requires-Dist: mkdocs-codeinclude-plugin~=0.2.1; extra == "docs"
51
+ Requires-Dist: markdown-include~=0.8.1; extra == "docs"
52
+ Requires-Dist: mkdocs-exclude~=1.0.2; extra == "docs"
53
+ Dynamic: license-file
54
+
55
+ **Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
56
+
57
+ **Github**: <https://github.com/RecordedFuture-ProfessionalServices/psengine>
58
+
59
+ **PyPi**: <https://pypi.org/project/psengine/>
60
+
61
+ ---
62
+
63
+ PSEngine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
64
+
65
+ PSEngine allows you to interact with the Recorded Future API extremely easily. There’s no need to manually build the URLs and query parameters, just use the modules dedicated to individual API endpoints.
66
+
67
+ PSEngine is a Python package solely built and maintained by the Cyber Security Engineering team powering a number of high profile integrations, such as: Elasticsearch, QRadar, Anomali, Jira, TheHive, etc.
68
+
69
+ ## Installation
70
+
71
+ PSEngine is a Python package that can be installed using `pip`. To install PSengine, run the following command:
72
+
73
+ ```bash
74
+ pip install psengine
75
+ ```
76
+
77
+ PSEngine officially supports Python >= 3.9, < 3.14.
78
+
79
+
80
+ ## Supported Features & Best Practices
81
+
82
+ PSEngine is ready for the demands of building robust and reliable integrations.
83
+
84
+ It can easily interact with the following Recorded Future datasets:
85
+
86
+ * Analyst Notes
87
+ * Collective Insights
88
+ * Classic & Playbook Alerts
89
+ * Detection Rules
90
+ - Identity Exposures management
91
+ * List management
92
+ * On demand IOC enrichment
93
+ * Risklists
94
+ - STIX conversion
95
+
96
+
97
+ And facilitate the development with features like:
98
+
99
+ * Built-in logging
100
+ * Easy configuration management
101
+ - Markdown creation from certain data types
102
+ * Proxy support
103
+
@@ -0,0 +1,49 @@
1
+ **Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
2
+
3
+ **Github**: <https://github.com/RecordedFuture-ProfessionalServices/psengine>
4
+
5
+ **PyPi**: <https://pypi.org/project/psengine/>
6
+
7
+ ---
8
+
9
+ PSEngine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
10
+
11
+ PSEngine allows you to interact with the Recorded Future API extremely easily. There’s no need to manually build the URLs and query parameters, just use the modules dedicated to individual API endpoints.
12
+
13
+ PSEngine is a Python package solely built and maintained by the Cyber Security Engineering team powering a number of high profile integrations, such as: Elasticsearch, QRadar, Anomali, Jira, TheHive, etc.
14
+
15
+ ## Installation
16
+
17
+ PSEngine is a Python package that can be installed using `pip`. To install PSengine, run the following command:
18
+
19
+ ```bash
20
+ pip install psengine
21
+ ```
22
+
23
+ PSEngine officially supports Python >= 3.9, < 3.14.
24
+
25
+
26
+ ## Supported Features & Best Practices
27
+
28
+ PSEngine is ready for the demands of building robust and reliable integrations.
29
+
30
+ It can easily interact with the following Recorded Future datasets:
31
+
32
+ * Analyst Notes
33
+ * Collective Insights
34
+ * Classic & Playbook Alerts
35
+ * Detection Rules
36
+ - Identity Exposures management
37
+ * List management
38
+ * On demand IOC enrichment
39
+ * Risklists
40
+ - STIX conversion
41
+
42
+
43
+ And facilitate the development with features like:
44
+
45
+ * Built-in logging
46
+ * Easy configuration management
47
+ - Markdown creation from certain data types
48
+ * Proxy support
49
+
@@ -11,4 +11,4 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- __version__ = '2.1.0'
14
+ __version__ = '2.2.0'
@@ -14,9 +14,10 @@
14
14
  import json
15
15
  import logging
16
16
  from pathlib import Path
17
- from typing import Union
17
+ from typing import Annotated, Union
18
18
 
19
19
  from pydantic import validate_call
20
+ from typing_extensions import Doc
20
21
 
21
22
  from ..errors import WriteFileError
22
23
  from ..helpers import OSHelpers, debug_call
@@ -28,16 +29,14 @@ LOG = logging.getLogger('psengine.analyst_notes.helpers')
28
29
  @debug_call
29
30
  @validate_call
30
31
  def save_attachment(
31
- note_id: str, data: Union[bytes, str], ext: str, output_directory: Union[str, Path]
32
+ note_id: Annotated[str, Doc('The ID of the AnalystNote.')],
33
+ data: Annotated[Union[bytes, str], Doc('The data returned from `fetch_attachment`.')],
34
+ ext: Annotated[str, Doc('The extension of the attachment, returned by `fetch_attachment`.')],
35
+ output_directory: Annotated[Union[str, Path], Doc('The directory to save the file into.')],
32
36
  ) -> None:
33
- """Save yara, sigma, snort or pdf to file. The file will use the extension provided and the
34
- ``note_id`` to create the filename.
37
+ """Save a YARA, Sigma, Snort, or PDF attachment to a file.
35
38
 
36
- Args:
37
- note_id (str): ``AnalystNote`` id
38
- data (Union[bytes, str]): data, returned from ``fetch_attachment``
39
- ext (str): extension of the attachment, returned by ``fetch_attachment``
40
- output_directory (str, Path): the directory to save the file into
39
+ The file will use the provided extension and the `note_id` to create the filename.
41
40
  """
42
41
  output_directory = (
43
42
  output_directory if isinstance(output_directory, str) else output_directory.as_posix()
@@ -47,13 +46,11 @@ def save_attachment(
47
46
 
48
47
  @debug_call
49
48
  @validate_call
50
- def save_note(note: AnalystNote, output_directory: Union[str, Path]) -> None:
51
- """Save AnalystNote object on file. The file will have the name of the note id.
52
-
53
- Args:
54
- note (AnalystNote): note to save.
55
- output_directory (str, Path): the directory to save the file into
56
- """
49
+ def save_note(
50
+ note: Annotated[AnalystNote, Doc('The note to save.')],
51
+ output_directory: Annotated[Union[str, Path], Doc('The directory to save the file into.')],
52
+ ) -> None:
53
+ """Save an `AnalystNote` object to a file named with the note ID."""
57
54
  output_directory = (
58
55
  output_directory if isinstance(output_directory, str) else output_directory.as_posix()
59
56
  )
@@ -70,12 +67,6 @@ def _save_attachment(
70
67
  ) -> None:
71
68
  """Save attachment from bytes or note itself from json.
72
69
 
73
- Args:
74
- note_id (str): id of the note, will be the filename
75
- data (bytes | str): content to save
76
- ext (str): extension of the file.
77
- output_directory (str): the directory to save the file into
78
-
79
70
  Raises:
80
71
  WriteFileError: if saving to file fails
81
72
  """
@@ -13,11 +13,12 @@
13
13
 
14
14
  import logging
15
15
  from datetime import datetime
16
- from typing import Any, Optional, Union
16
+ from typing import Annotated, Any, Optional, Union
17
17
 
18
- from pydantic import Field, ValidationError, field_validator, model_validator
18
+ from pydantic import BeforeValidator, Field, ValidationError, field_validator, model_validator
19
19
 
20
20
  from ..common_models import IdNameType, IdNameTypeDescription, RFBaseModel
21
+ from ..helpers import Validators
21
22
 
22
23
 
23
24
  class DiamondModel(RFBaseModel):
@@ -189,7 +190,7 @@ class Attributes(RFBaseModel):
189
190
  @field_validator('events', mode='after')
190
191
  @classmethod
191
192
  def remove_empty_events(cls, values):
192
- """Remove empty events when ``NoteEvent`` skip the validation."""
193
+ """Remove empty events when `NoteEvent` skip the validation."""
193
194
  return [v for v in values if v.type_ and v.attributes]
194
195
 
195
196
 
@@ -198,7 +199,10 @@ class PreviewAttributesIn(RFBaseModel):
198
199
  text: str
199
200
  note_entities: Optional[list[str]] = []
200
201
  context_entities: Optional[list[str]] = []
201
- topic: Union[list[str], str, None] = []
202
+ topic: Annotated[
203
+ Union[list[str], str, None],
204
+ BeforeValidator(Validators.convert_str_to_list),
205
+ ] = []
202
206
  labels: Optional[list[str]] = []
203
207
  validation_urls: Optional[list[str]] = []
204
208
 
@@ -12,9 +12,10 @@
12
12
  ##############################################################################################
13
13
 
14
14
  from functools import total_ordering
15
- from typing import Optional, Union
15
+ from typing import Annotated, Optional, Union
16
16
 
17
17
  from pydantic import Field
18
+ from typing_extensions import Doc
18
19
 
19
20
  from ..common_models import IdNameTypeDescription, RFBaseModel
20
21
  from ..constants import TIMESTAMP_STR
@@ -26,32 +27,34 @@ from .models import Attributes, PreviewAttributesIn, PreviewAttributesOut, Reque
26
27
  # AnalystNote also used by BaseEnrichedEntity
27
28
  @total_ordering
28
29
  class AnalystNote(RFBaseModel):
29
- """Validate data received from ``/search``, ``/analystnote/{note}`` endpoints.
30
+ """Validate data received from the `/search` and `/analystnote/{note}` endpoints.
30
31
 
31
- Methods:
32
- __hash__:
33
- Returns a hash value based on note ``id_``.
32
+ This class supports hashing, equality comparison, string representation, and total ordering
33
+ of `AnalystNote` instances.
34
34
 
35
- __eq__:
36
- Checks equality between two AnalystNote instances based on ``id_`` and published time.
35
+ Hashing:
36
+ Returns a hash value based on the note `id_`.
37
37
 
38
- __gt__:
39
- Defines a greater-than comparison between two AnalystNote instances based published time
40
- and the ``id_``
38
+ Equality:
39
+ Checks equality between two `AnalystNote` instances based on the `id_` and published time.
41
40
 
42
- __str__:
43
- Returns a string representation of the AnalystNote instance with:
44
- ``id_``, ``title``, and published timestamp.
41
+ Greater-than Comparison:
42
+ Defines a greater-than comparison between two `AnalystNote` instances based on the published
43
+ time and the `id_`.
45
44
 
46
- .. code-block:: python
45
+ String Representation:
46
+ Returns a string representation of the `AnalystNote` instance including the `id_`, `title`,
47
+ and published timestamp.
47
48
 
48
- >>> print(analyst_note)
49
- Analyst Note ID: 12345, Title: Cyber Vuln, Published: 2024-05-21 10:42:30AM
49
+ ```python
50
+ >>> print(analyst_note)
51
+ Analyst Note ID: 12345, Title: Cyber Vuln, Published: 2024-05-21 10:42:30AM
52
+ ```
50
53
 
51
- Total Ordering:
52
- The ordering of AnalystNote instances is determined primarily by the published timestamp in
53
- the attributes. If two instances have the same published timestamp, the note id is used as
54
- a secondary criterion for ordering.
54
+ Ordering:
55
+ The ordering of `AnalystNote` instances is determined primarily by the published timestamp.
56
+ If two instances have the same published timestamp, the note `id_` is used as a secondary
57
+ criterion.
55
58
  """
56
59
 
57
60
  external_id: Optional[str] = None
@@ -77,7 +80,7 @@ class AnalystNote(RFBaseModel):
77
80
  @property
78
81
  def detection_rule_type(self) -> Optional[str]:
79
82
  """Returns the attachment type if present, else None. It checks for specific types like
80
- 'sigma rule', 'yara rule', and 'snort rule' in the topics of the note.
83
+ `sigma rule`, `yara rule`, and `snort rule` in the topics of the note.
81
84
  """
82
85
  topics_type = ('sigma rule', 'yara rule', 'snort rule')
83
86
 
@@ -101,26 +104,20 @@ class AnalystNote(RFBaseModel):
101
104
 
102
105
  def markdown(
103
106
  self,
104
- extract_entities: bool = True,
105
- diamond_model: bool = True,
106
- html_tags: bool = False,
107
- defang_malicious_infrastructure: bool = False,
108
- character_limit: int = None,
109
- ) -> str:
110
- """Return the markdown representation of the Note.
111
-
112
- Args:
113
- extract_entities (bool): Extract and include entities in the markdown. Defaults to True.
114
- diamond_model (bool): Include a diamond model visualization. Defaults to True.
115
- html_tags (bool): Include HTML tags in the output. Defaults to False.
116
- defang_malicious_infrastructure (bool): Defang URLs or other malicious indicators.
117
- Defaults to False.
118
- character_limit (int, optional): Limit the output to a specified number of characters.
119
- Defaults to None.
120
-
121
- Returns:
122
- str: The generated markdown string.
123
- """
107
+ extract_entities: Annotated[
108
+ bool, Doc('Extract and include entities in the markdown.')
109
+ ] = True,
110
+ diamond_model: Annotated[bool, Doc('Include a diamond model visualization.')] = True,
111
+ html_tags: Annotated[bool, Doc('Include HTML tags in the output.')] = False,
112
+ defang_malicious_infrastructure: Annotated[
113
+ bool, Doc('Defang URLs or other malicious indicators.')
114
+ ] = False,
115
+ character_limit: Annotated[
116
+ Optional[int],
117
+ Doc('Limit the output to a specified number of characters.'),
118
+ ] = None,
119
+ ) -> Annotated[str, Doc('The generated markdown string.')]:
120
+ """Return the markdown representation of the note."""
124
121
  return _markdown(
125
122
  self,
126
123
  extract_entities=extract_entities,
@@ -132,7 +129,7 @@ class AnalystNote(RFBaseModel):
132
129
 
133
130
 
134
131
  class AnalystNotePreviewIn(RFBaseModel):
135
- """Validate data sent to ``/preview`` endpoint."""
132
+ """Validate data sent to `/preview` endpoint."""
136
133
 
137
134
  attributes: PreviewAttributesIn
138
135
  source: Optional[str]
@@ -141,14 +138,14 @@ class AnalystNotePreviewIn(RFBaseModel):
141
138
 
142
139
 
143
140
  class AnalystNotePreviewOut(RFBaseModel):
144
- """Validate data received from ``/preview`` endpoint."""
141
+ """Validate data received from `/preview` endpoint."""
145
142
 
146
143
  attributes: PreviewAttributesOut
147
144
  source: IdNameTypeDescription
148
145
 
149
146
 
150
147
  class AnalystNotePublishIn(AnalystNotePreviewIn):
151
- """Validate data sent to ``/publish`` endpoint."""
148
+ """Validate data sent to `/publish` endpoint."""
152
149
 
153
150
  attributes: PreviewAttributesIn
154
151
  source: Optional[str] = None
@@ -160,13 +157,13 @@ class AnalystNotePublishIn(AnalystNotePreviewIn):
160
157
 
161
158
 
162
159
  class AnalystNotePublishOut(RFBaseModel):
163
- """Validate data received from ``/publish`` endpoint."""
160
+ """Validate data received from `/publish` endpoint."""
164
161
 
165
162
  note_id: str
166
163
 
167
164
 
168
165
  class AnalystNoteSearchIn(RFBaseModel):
169
- """Validate data sent to ``/search`` endpoint."""
166
+ """Validate data sent to `/search` endpoint."""
170
167
 
171
168
  published: Optional[str] = None
172
169
  entity: Optional[str] = None