psengine 2.5.1__tar.gz → 2.6.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 (164) hide show
  1. {psengine-2.5.1 → psengine-2.6.0}/PKG-INFO +23 -40
  2. {psengine-2.5.1 → psengine-2.6.0}/README.md +10 -2
  3. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/helpers.py +5 -7
  4. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/models.py +64 -60
  5. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/note.py +16 -16
  6. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/note_mgr.py +25 -25
  7. {psengine-2.5.1 → psengine-2.6.0}/psengine/asi/asi.py +20 -21
  8. {psengine-2.5.1 → psengine-2.6.0}/psengine/asi/asi_mgr.py +133 -137
  9. {psengine-2.5.1 → psengine-2.6.0}/psengine/asi/client.py +15 -15
  10. psengine-2.6.0/psengine/asi/models.py +341 -0
  11. {psengine-2.5.1 → psengine-2.6.0}/psengine/base_http_client.py +10 -9
  12. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/classic_alert.py +11 -11
  13. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/classic_alert_mgr.py +35 -32
  14. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/helpers.py +3 -3
  15. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/models.py +27 -28
  16. {psengine-2.5.1 → psengine-2.6.0}/psengine/collective_insights/collective_insights.py +13 -13
  17. {psengine-2.5.1 → psengine-2.6.0}/psengine/collective_insights/insight.py +6 -8
  18. {psengine-2.5.1 → psengine-2.6.0}/psengine/collective_insights/models.py +6 -7
  19. {psengine-2.5.1 → psengine-2.6.0}/psengine/common_models.py +4 -4
  20. {psengine-2.5.1 → psengine-2.6.0}/psengine/config/config.py +22 -21
  21. {psengine-2.5.1 → psengine-2.6.0}/psengine/detection/detection_mgr.py +13 -15
  22. {psengine-2.5.1 → psengine-2.6.0}/psengine/detection/detection_rule.py +4 -5
  23. {psengine-2.5.1 → psengine-2.6.0}/psengine/detection/helpers.py +2 -2
  24. {psengine-2.5.1 → psengine-2.6.0}/psengine/detection/models.py +12 -12
  25. {psengine-2.5.1 → psengine-2.6.0}/psengine/endpoints.py +13 -4
  26. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/lookup.py +59 -60
  27. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/lookup_mgr.py +4 -6
  28. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/models/base_enriched_entity.py +9 -10
  29. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/models/lookup.py +81 -86
  30. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/models/soar.py +7 -8
  31. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/soar.py +7 -8
  32. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/soar_mgr.py +9 -11
  33. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_lists/entity_list.py +12 -12
  34. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_lists/entity_list_mgr.py +6 -8
  35. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_lists/models.py +8 -9
  36. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_match/entity_match.py +2 -3
  37. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_match/entity_match_mgr.py +11 -15
  38. {psengine-2.5.1 → psengine-2.6.0}/psengine/fusion/fusion_mgr.py +4 -4
  39. {psengine-2.5.1 → psengine-2.6.0}/psengine/fusion/models.py +11 -12
  40. {psengine-2.5.1 → psengine-2.6.0}/psengine/helpers/helpers.py +49 -27
  41. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/constants.py +1 -1
  42. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/identity.py +28 -28
  43. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/identity_mgr.py +111 -121
  44. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/models/common_models.py +44 -44
  45. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/models/detections.py +18 -18
  46. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/models/incident_report.py +5 -6
  47. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/models/lookup.py +7 -8
  48. {psengine-2.5.1 → psengine-2.6.0}/psengine/logger/rf_logger.py +2 -2
  49. {psengine-2.5.1 → psengine-2.6.0}/psengine/malware_intel/__init__.py +34 -2
  50. psengine-2.6.0/psengine/malware_intel/auto_sigma_mgr.py +242 -0
  51. psengine-2.6.0/psengine/malware_intel/auto_yara_mgr.py +226 -0
  52. psengine-2.6.0/psengine/malware_intel/constants.py +16 -0
  53. psengine-2.6.0/psengine/malware_intel/errors.py +66 -0
  54. psengine-2.6.0/psengine/malware_intel/helpers.py +85 -0
  55. {psengine-2.5.1 → psengine-2.6.0}/psengine/malware_intel/malware_intel.py +146 -6
  56. {psengine-2.5.1 → psengine-2.6.0}/psengine/malware_intel/malware_intel_mgr.py +2 -2
  57. psengine-2.6.0/psengine/malware_intel/models.py +357 -0
  58. {psengine-2.5.1 → psengine-2.6.0}/psengine/markdown/markdown.py +3 -5
  59. {psengine-2.5.1 → psengine-2.6.0}/psengine/markdown/models.py +1 -3
  60. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/constants.py +16 -14
  61. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/helpers.py +11 -12
  62. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +3 -3
  63. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +1 -1
  64. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +5 -5
  65. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/common_models.py +5 -6
  66. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/panel_log.py +74 -75
  67. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/panel_status.py +18 -19
  68. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +10 -11
  69. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +10 -11
  70. psengine-2.6.0/psengine/playbook_alerts/models/pba_domain_abuse.py +138 -0
  71. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +4 -5
  72. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +33 -34
  73. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/pba_malware_report.py +7 -8
  74. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +16 -18
  75. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/search_endpoint.py +6 -7
  76. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/playbook_alert_mgr.py +80 -82
  77. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/playbook_alerts.py +60 -51
  78. {psengine-2.5.1 → psengine-2.6.0}/psengine/rf_client.py +13 -15
  79. {psengine-2.5.1 → psengine-2.6.0}/psengine/risk_history/models.py +15 -15
  80. {psengine-2.5.1 → psengine-2.6.0}/psengine/risk_history/risk_history_mgr.py +4 -4
  81. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/complex_entity.py +2 -2
  82. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/helpers.py +2 -2
  83. {psengine-2.5.1 → psengine-2.6.0}/pyproject.toml +70 -41
  84. psengine-2.5.1/LICENSE +0 -21
  85. psengine-2.5.1/psengine/asi/models.py +0 -345
  86. psengine-2.5.1/psengine/malware_intel/errors.py +0 -18
  87. psengine-2.5.1/psengine/malware_intel/models.py +0 -275
  88. psengine-2.5.1/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -139
  89. psengine-2.5.1/psengine.egg-info/PKG-INFO +0 -110
  90. psengine-2.5.1/psengine.egg-info/SOURCES.txt +0 -154
  91. psengine-2.5.1/psengine.egg-info/dependency_links.txt +0 -1
  92. psengine-2.5.1/psengine.egg-info/requires.txt +0 -35
  93. psengine-2.5.1/psengine.egg-info/top_level.txt +0 -1
  94. psengine-2.5.1/setup.cfg +0 -4
  95. {psengine-2.5.1 → psengine-2.6.0}/psengine/__init__.py +0 -0
  96. {psengine-2.5.1 → psengine-2.6.0}/psengine/_sdk_id.py +0 -0
  97. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/__init__.py +0 -0
  98. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/constants.py +0 -0
  99. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/errors.py +0 -0
  100. {psengine-2.5.1 → psengine-2.6.0}/psengine/analyst_notes/markdown.py +0 -0
  101. {psengine-2.5.1 → psengine-2.6.0}/psengine/asi/__init__.py +0 -0
  102. {psengine-2.5.1 → psengine-2.6.0}/psengine/asi/constants.py +0 -0
  103. {psengine-2.5.1 → psengine-2.6.0}/psengine/asi/errors.py +0 -0
  104. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/__init__.py +0 -0
  105. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/constants.py +0 -0
  106. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/errors.py +0 -0
  107. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  108. {psengine-2.5.1 → psengine-2.6.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
  109. {psengine-2.5.1 → psengine-2.6.0}/psengine/collective_insights/__init__.py +0 -0
  110. {psengine-2.5.1 → psengine-2.6.0}/psengine/collective_insights/constants.py +0 -0
  111. {psengine-2.5.1 → psengine-2.6.0}/psengine/collective_insights/errors.py +0 -0
  112. {psengine-2.5.1 → psengine-2.6.0}/psengine/config/__init__.py +0 -0
  113. {psengine-2.5.1 → psengine-2.6.0}/psengine/config/errors.py +0 -0
  114. {psengine-2.5.1 → psengine-2.6.0}/psengine/constants.py +0 -0
  115. {psengine-2.5.1 → psengine-2.6.0}/psengine/detection/__init__.py +0 -0
  116. {psengine-2.5.1 → psengine-2.6.0}/psengine/detection/errors.py +0 -0
  117. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/__init__.py +0 -0
  118. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/constants.py +0 -0
  119. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/errors.py +0 -0
  120. {psengine-2.5.1 → psengine-2.6.0}/psengine/enrich/models/__init__.py +0 -0
  121. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_lists/__init__.py +0 -0
  122. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_lists/constants.py +0 -0
  123. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_lists/errors.py +0 -0
  124. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_match/__init__.py +0 -0
  125. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_match/errors.py +0 -0
  126. {psengine-2.5.1 → psengine-2.6.0}/psengine/entity_match/models.py +0 -0
  127. {psengine-2.5.1 → psengine-2.6.0}/psengine/errors.py +0 -0
  128. {psengine-2.5.1 → psengine-2.6.0}/psengine/fusion/__init__.py +0 -0
  129. {psengine-2.5.1 → psengine-2.6.0}/psengine/fusion/errors.py +0 -0
  130. {psengine-2.5.1 → psengine-2.6.0}/psengine/helpers/__init__.py +0 -0
  131. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/__init__.py +0 -0
  132. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/errors.py +0 -0
  133. {psengine-2.5.1 → psengine-2.6.0}/psengine/identity/models/__init__.py +0 -0
  134. {psengine-2.5.1 → psengine-2.6.0}/psengine/logger/__init__.py +0 -0
  135. {psengine-2.5.1 → psengine-2.6.0}/psengine/logger/constants.py +0 -0
  136. {psengine-2.5.1 → psengine-2.6.0}/psengine/logger/errors.py +0 -0
  137. {psengine-2.5.1 → psengine-2.6.0}/psengine/markdown/__init__.py +0 -0
  138. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/__init__.py +0 -0
  139. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/errors.py +0 -0
  140. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/mappings.py +0 -0
  141. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  142. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  143. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  144. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  145. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  146. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  147. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  148. {psengine-2.5.1 → psengine-2.6.0}/psengine/playbook_alerts/pa_category.py +0 -0
  149. {psengine-2.5.1 → psengine-2.6.0}/psengine/py.typed +0 -0
  150. {psengine-2.5.1 → psengine-2.6.0}/psengine/risk_history/__init__.py +0 -0
  151. {psengine-2.5.1 → psengine-2.6.0}/psengine/risk_history/errors.py +0 -0
  152. {psengine-2.5.1 → psengine-2.6.0}/psengine/risklists/__init__.py +0 -0
  153. {psengine-2.5.1 → psengine-2.6.0}/psengine/risklists/constants.py +0 -0
  154. {psengine-2.5.1 → psengine-2.6.0}/psengine/risklists/errors.py +0 -0
  155. {psengine-2.5.1 → psengine-2.6.0}/psengine/risklists/models.py +0 -0
  156. {psengine-2.5.1 → psengine-2.6.0}/psengine/risklists/risklist_mgr.py +0 -0
  157. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/__init__.py +0 -0
  158. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/base_stix_entity.py +0 -0
  159. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/constants.py +0 -0
  160. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/enriched_indicator.py +0 -0
  161. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/errors.py +0 -0
  162. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/rf_bundle.py +0 -0
  163. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/simple_entity.py +0 -0
  164. {psengine-2.5.1 → psengine-2.6.0}/psengine/stix2/util.py +0 -0
@@ -1,59 +1,34 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.5.1
3
+ Version: 2.6.0
4
4
  Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
5
+ Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
6
+ Author: Moise Medici, Patrick Kinsella, Ernest Bartosevic
5
7
  Author-email: Moise Medici <moise.medici@recordedfuture.com>, Patrick Kinsella <patrick.kinsella@recordedfuture.com>, Ernest Bartosevic <ernest.bartosevic@recordedfuture.com>
6
8
  License-Expression: MIT
7
- Project-URL: Homepage, https://recordedfuture-professionalservices.github.io/psengine/latest/
8
- Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/latest/CHANGELOG/
9
- Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: Programming Language :: Python
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: Topic :: Security
14
13
  Classifier: Topic :: Software Development :: Libraries
15
- Classifier: Programming Language :: Python :: 3.9
16
14
  Classifier: Programming Language :: Python :: 3.10
17
15
  Classifier: Programming Language :: Python :: 3.11
18
16
  Classifier: Programming Language :: Python :: 3.12
19
17
  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: typing_extensions>=4.8.0
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Dist: typing-extensions>=4.8.0
24
20
  Requires-Dist: requests>=2.27.1
25
- Requires-Dist: jsonpath_ng<=1.6.1,>=1.5.3
21
+ Requires-Dist: jsonpath-ng>=1.5.3,<=1.6.1
26
22
  Requires-Dist: stix2~=3.0.1
27
23
  Requires-Dist: python-dateutil>=2.7.0
28
- Requires-Dist: more-itertools<=10.2.0,>=9.0.0
29
- Requires-Dist: pydantic<3.0.0,>=2.7
30
- Requires-Dist: pydantic-settings[toml]<2.11.0,>=2.5.2
24
+ Requires-Dist: more-itertools>=9.0.0,<=10.2.0
25
+ Requires-Dist: pydantic>=2.7,<3.0.0
26
+ Requires-Dist: pydantic-settings[toml]>=2.12.2,<2.13.1
31
27
  Requires-Dist: markdown-strings==3.4.0
32
- Provides-Extra: dev
33
- Requires-Dist: pytest==8.3.4; extra == "dev"
34
- Requires-Dist: pytest-cov==6.0.0; extra == "dev"
35
- Requires-Dist: pytest-mock==3.14.0; extra == "dev"
36
- Requires-Dist: pytest-md==0.2.0; extra == "dev"
37
- Requires-Dist: pytest-random-order==1.1.1; extra == "dev"
38
- Requires-Dist: pytest-httpdbg==0.9.0; extra == "dev"
39
- Requires-Dist: ruff~=0.11.0; extra == "dev"
40
- Requires-Dist: mimesis>=12.1.0; extra == "dev"
41
- Requires-Dist: build==1.3.0; extra == "dev"
42
- Requires-Dist: wheel==0.45.1; extra == "dev"
43
- Requires-Dist: setuptools==80.9.0; extra == "dev"
44
- Provides-Extra: docs
45
- Requires-Dist: ruff~=0.11.0; extra == "docs"
46
- Requires-Dist: mike~=2.1.3; extra == "docs"
47
- Requires-Dist: mkdocs~=1.6.1; extra == "docs"
48
- Requires-Dist: mkdocs-material~=9.6.18; extra == "docs"
49
- Requires-Dist: mkdocstrings[python]>=0.18; extra == "docs"
50
- Requires-Dist: griffe-typingdoc~=0.2.8; extra == "docs"
51
- Requires-Dist: mkdocs-codeinclude-plugin~=0.2.1; extra == "docs"
52
- Requires-Dist: markdown-include~=0.8.1; extra == "docs"
53
- Requires-Dist: mkdocs-exclude~=1.0.2; extra == "docs"
54
- Requires-Dist: rich; extra == "docs"
55
- Requires-Dist: logging-tree; extra == "docs"
56
- Dynamic: license-file
28
+ Requires-Python: >=3.10, <3.15
29
+ Project-URL: Homepage, https://recordedfuture-professionalservices.github.io/psengine/latest/
30
+ Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/latest/CHANGELOG/
31
+ Description-Content-Type: text/markdown
57
32
 
58
33
  **Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
59
34
 
@@ -67,7 +42,8 @@ PSEngine is a simple, yet elegant, library for rapid development of integrations
67
42
 
68
43
  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.
69
44
 
70
- 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.
45
+ PSEngine is a Python package solely built and maintained by the Recorded Future Cyber Security Engineering team powering a number of high profile integrations, such as: [Banshee](https://recordedfuture-professionalservices.github.io/banshee); [Recorded Future Alerts for QRadar](https://apps.xforce.ibmcloud.com/extension/b36efdf42b7bf5e3759d036dbcdbf606); Anomali ThreatStream: [Alerts](https://support.recordedfuture.com/hc/en-us/articles/29255683708691-Recorded-Future-Alerts-for-Anomali-ThreatStream) and [Analyst Notes](https://support.recordedfuture.com/hc/en-us/articles/12928414947475-Recorded-Future-Analyst-Notes-for-Anomali-ThreatStream) integrations; [Google SecOps](https://app.recordedfuture.com/portal/integration-center/detail/google-chronicle-nbfi?organization=uhash%3A5cJsHMHeSM&filter_tab=all) and many more.
46
+
71
47
 
72
48
  ## Installation
73
49
 
@@ -77,7 +53,7 @@ PSEngine is a Python package that can be installed using `pip`. To install PSeng
77
53
  pip install psengine
78
54
  ```
79
55
 
80
- PSEngine officially supports Python >= 3.9, < 3.14.
56
+ PSEngine officially supports Python >= 3.10, < 3.15.
81
57
 
82
58
 
83
59
  ## Supported Features & Best Practices
@@ -94,6 +70,7 @@ It can easily interact with the following Recorded Future datasets:
94
70
  - Fusion File management
95
71
  - Identity Exposures management
96
72
  - List management
73
+ - Malware Intelligence (including Auto Yara and Auto Sigma)
97
74
  - Malware Sandbox reports download
98
75
  - On demand IOC enrichment
99
76
  - Risklists
@@ -108,3 +85,9 @@ And facilitate the development with features like:
108
85
  - Markdown creation from certain data types
109
86
  - Proxy support
110
87
 
88
+ ## Previous versions and version documentation
89
+
90
+ PSEngine has been made public from our internal version 2.0.4. Previous versions, including version 1, are not publicly available.
91
+
92
+ 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.
93
+
@@ -10,7 +10,8 @@ PSEngine is a simple, yet elegant, library for rapid development of integrations
10
10
 
11
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
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.
13
+ PSEngine is a Python package solely built and maintained by the Recorded Future Cyber Security Engineering team powering a number of high profile integrations, such as: [Banshee](https://recordedfuture-professionalservices.github.io/banshee); [Recorded Future Alerts for QRadar](https://apps.xforce.ibmcloud.com/extension/b36efdf42b7bf5e3759d036dbcdbf606); Anomali ThreatStream: [Alerts](https://support.recordedfuture.com/hc/en-us/articles/29255683708691-Recorded-Future-Alerts-for-Anomali-ThreatStream) and [Analyst Notes](https://support.recordedfuture.com/hc/en-us/articles/12928414947475-Recorded-Future-Analyst-Notes-for-Anomali-ThreatStream) integrations; [Google SecOps](https://app.recordedfuture.com/portal/integration-center/detail/google-chronicle-nbfi?organization=uhash%3A5cJsHMHeSM&filter_tab=all) and many more.
14
+
14
15
 
15
16
  ## Installation
16
17
 
@@ -20,7 +21,7 @@ PSEngine is a Python package that can be installed using `pip`. To install PSeng
20
21
  pip install psengine
21
22
  ```
22
23
 
23
- PSEngine officially supports Python >= 3.9, < 3.14.
24
+ PSEngine officially supports Python >= 3.10, < 3.15.
24
25
 
25
26
 
26
27
  ## Supported Features & Best Practices
@@ -37,6 +38,7 @@ It can easily interact with the following Recorded Future datasets:
37
38
  - Fusion File management
38
39
  - Identity Exposures management
39
40
  - List management
41
+ - Malware Intelligence (including Auto Yara and Auto Sigma)
40
42
  - Malware Sandbox reports download
41
43
  - On demand IOC enrichment
42
44
  - Risklists
@@ -51,3 +53,9 @@ And facilitate the development with features like:
51
53
  - Markdown creation from certain data types
52
54
  - Proxy support
53
55
 
56
+ ## Previous versions and version documentation
57
+
58
+ PSEngine has been made public from our internal version 2.0.4. Previous versions, including version 1, are not publicly available.
59
+
60
+ 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.
61
+
@@ -14,7 +14,7 @@
14
14
  import json
15
15
  import logging
16
16
  from pathlib import Path
17
- from typing import Annotated, Union
17
+ from typing import Annotated
18
18
 
19
19
  from pydantic import validate_call
20
20
  from typing_extensions import Doc
@@ -30,9 +30,9 @@ LOG = logging.getLogger('psengine.analyst_notes.helpers')
30
30
  @validate_call
31
31
  def save_attachment(
32
32
  note_id: Annotated[str, Doc('The ID of the AnalystNote.')],
33
- data: Annotated[Union[bytes, str], Doc('The data returned from `fetch_attachment`.')],
33
+ data: Annotated[bytes | str, Doc('The data returned from `fetch_attachment`.')],
34
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.')],
35
+ output_directory: Annotated[str | Path, Doc('The directory to save the file into.')],
36
36
  ) -> None:
37
37
  """Save a YARA, Sigma, Snort, or PDF attachment to a file.
38
38
 
@@ -48,7 +48,7 @@ def save_attachment(
48
48
  @validate_call
49
49
  def save_note(
50
50
  note: Annotated[AnalystNote, Doc('The note to save.')],
51
- output_directory: Annotated[Union[str, Path], Doc('The directory to save the file into.')],
51
+ output_directory: Annotated[str | Path, Doc('The directory to save the file into.')],
52
52
  ) -> None:
53
53
  """Save an `AnalystNote` object to a file named with the note ID."""
54
54
  output_directory = (
@@ -62,9 +62,7 @@ def save_note(
62
62
  )
63
63
 
64
64
 
65
- def _save_attachment(
66
- note_id: str, data: Union[bytes, str], ext: str, output_directory: str
67
- ) -> None:
65
+ def _save_attachment(note_id: str, data: bytes | str, ext: str, output_directory: str) -> None:
68
66
  """Save attachment from bytes or note itself from json.
69
67
 
70
68
  Raises:
@@ -13,7 +13,7 @@
13
13
 
14
14
  import logging
15
15
  from datetime import datetime
16
- from typing import Annotated, Any, Optional, Union
16
+ from typing import Annotated, Any
17
17
 
18
18
  from pydantic import BeforeValidator, Field, ValidationError, field_validator, model_validator
19
19
 
@@ -21,18 +21,22 @@ from ..common_models import IdNameType, IdNameTypeDescription, RFBaseModel
21
21
  from ..helpers import Validators
22
22
 
23
23
 
24
+ class NoteEntity(IdNameTypeDescription):
25
+ is_threat_actor: bool | None = None
26
+
27
+
24
28
  class DiamondModel(RFBaseModel):
25
- start: Optional[datetime] = None
26
- stop: Optional[datetime] = None
27
- malicious_infrastructure: Optional[list[IdNameTypeDescription]] = []
28
- capabilities: Optional[list[IdNameTypeDescription]] = []
29
- adversary: Optional[list[IdNameTypeDescription]] = []
30
- target: Optional[list[IdNameTypeDescription]] = []
29
+ start: datetime | None = None
30
+ stop: datetime | None = None
31
+ malicious_infrastructure: list[NoteEntity] | None = []
32
+ capabilities: list[NoteEntity] | None = []
33
+ adversary: list[NoteEntity] | None = []
34
+ target: list[NoteEntity] | None = []
31
35
 
32
36
 
33
37
  class Query(RFBaseModel):
34
38
  title: str
35
- url: Optional[IdNameTypeDescription] = None
39
+ url: NoteEntity | None = None
36
40
 
37
41
 
38
42
  class Position(RFBaseModel):
@@ -43,35 +47,35 @@ class Position(RFBaseModel):
43
47
  class PositionEvent(RFBaseModel):
44
48
  start: datetime
45
49
  stop: datetime
46
- location: Optional[list[IdNameTypeDescription]] = []
47
- event_positions: Optional[list[Position]] = []
50
+ location: list[NoteEntity] | None = []
51
+ event_positions: list[Position] | None = []
48
52
 
49
53
 
50
54
  class CyberAttackEvent(RFBaseModel):
51
55
  start: datetime
52
56
  stop: datetime
53
- adversary: Optional[list[IdNameTypeDescription]] = []
54
- target: Optional[list[IdNameTypeDescription]] = []
55
- capabilities: list[IdNameTypeDescription] = []
56
- malicious_infrastructure: Optional[list[IdNameTypeDescription]] = []
57
- operation: Optional[list[IdNameTypeDescription]] = []
57
+ adversary: list[NoteEntity] | None = []
58
+ target: list[NoteEntity] | None = []
59
+ capabilities: list[NoteEntity] = []
60
+ malicious_infrastructure: list[NoteEntity] | None = []
61
+ operation: list[NoteEntity] | None = []
58
62
 
59
63
 
60
64
  class ArmedConflictEvent(PositionEvent):
61
- attacker: Optional[list[IdNameTypeDescription]] = []
62
- target: Optional[list[IdNameTypeDescription]] = []
65
+ attacker: list[NoteEntity] | None = []
66
+ target: list[NoteEntity] | None = []
63
67
 
64
68
 
65
69
  class ArmsPurchaseSaleEvent(RFBaseModel):
66
70
  start: datetime
67
71
  stop: datetime
68
- arms_seller: Optional[list[IdNameTypeDescription]] = []
69
- arms_purchaser: Optional[list[IdNameTypeDescription]] = []
72
+ arms_seller: list[NoteEntity] | None = []
73
+ arms_purchaser: list[NoteEntity] | None = []
70
74
 
71
75
 
72
76
  class DiseaseOutbreakEvent(PositionEvent):
73
- disease: Optional[list[IdNameTypeDescription]] = []
74
- facility: Optional[list[IdNameTypeDescription]] = []
77
+ disease: list[NoteEntity] | None = []
78
+ facility: list[NoteEntity] | None = []
75
79
 
76
80
 
77
81
  class EnvironmentalIssueEvent(PositionEvent):
@@ -79,45 +83,45 @@ class EnvironmentalIssueEvent(PositionEvent):
79
83
 
80
84
 
81
85
  class ManMadeDisasterEvent(PositionEvent):
82
- facility: list[IdNameTypeDescription]
83
- manmade_disaster: Union[list[IdNameTypeDescription], list[str]]
86
+ facility: list[NoteEntity]
87
+ manmade_disaster: list[NoteEntity] | list[str]
84
88
 
85
89
 
86
90
  class MilitaryManeuverEvent(PositionEvent):
87
- actors: Optional[list[IdNameTypeDescription]] = []
91
+ actors: list[NoteEntity] | None = []
88
92
 
89
93
 
90
94
  class NaturalDisasterEvent(PositionEvent):
91
- natural_disaster: list[IdNameTypeDescription]
95
+ natural_disaster: list[NoteEntity]
92
96
 
93
97
 
94
98
  class NuclearMaterialTransactionEvent(PositionEvent):
95
99
  material: list[str]
96
- location_origin: Optional[list[str]] = []
97
- location_destination: Optional[list[str]] = []
100
+ location_origin: list[str] | None = []
101
+ location_destination: list[str] | None = []
98
102
 
99
103
 
100
104
  class PersonThreatEvent(RFBaseModel):
101
105
  start: datetime
102
106
  stop: datetime
103
- threatened: list[IdNameTypeDescription]
104
- actor: Optional[list[IdNameTypeDescription]] = []
107
+ threatened: list[NoteEntity]
108
+ actor: list[NoteEntity] | None = []
105
109
 
106
110
 
107
111
  class ProtestEvent(RFBaseModel):
108
- protest_target: Optional[list[IdNameTypeDescription]] = []
112
+ protest_target: list[NoteEntity] | None = []
109
113
 
110
114
 
111
115
  class MalwareAnalysisEvent(RFBaseModel):
112
116
  start: datetime
113
117
  stop: datetime
114
- malware: list[IdNameTypeDescription]
115
- attacker: Optional[list[IdNameTypeDescription]] = []
116
- malicious_infrastructure: Optional[list[IdNameTypeDescription]] = []
117
- ttp: Optional[list[IdNameTypeDescription]] = []
118
- target: Optional[list[IdNameTypeDescription]] = []
119
- exploit: Optional[list[IdNameTypeDescription]] = []
120
- hash_: Optional[list[IdNameTypeDescription]] = Field(alias='hash', default=[])
118
+ malware: list[NoteEntity]
119
+ attacker: list[NoteEntity] | None = []
120
+ malicious_infrastructure: list[NoteEntity] | None = []
121
+ ttp: list[NoteEntity] | None = []
122
+ target: list[NoteEntity] | None = []
123
+ exploit: list[NoteEntity] | None = []
124
+ hash_: list[NoteEntity] | None = Field(alias='hash', default=[])
121
125
 
122
126
 
123
127
  ATTRIBUTES_MAPPING = {
@@ -143,8 +147,8 @@ ATTRIBUTES_MAPPING = {
143
147
 
144
148
 
145
149
  class NoteEvent(RFBaseModel):
146
- type_: Optional[str] = Field(alias='type', default=None)
147
- attributes: Optional[Any] = None
150
+ type_: str | None = Field(alias='type', default=None)
151
+ attributes: Any | None = None
148
152
 
149
153
  @model_validator(mode='before')
150
154
  @classmethod
@@ -175,17 +179,17 @@ class Attributes(RFBaseModel):
175
179
  title: str
176
180
  text: str
177
181
  published: datetime
178
- attachment: Optional[str] = None
179
- events: Optional[list[NoteEvent]] = []
180
- validated_on: Optional[datetime] = None
181
- note_entities: Optional[list[IdNameTypeDescription]] = []
182
- context_entities: Optional[list[IdNameTypeDescription]] = []
183
- topic: Optional[Union[list[IdNameTypeDescription], IdNameTypeDescription]] = []
184
- labels: Optional[list[IdNameTypeDescription]] = []
185
- validation_urls: Optional[list[IdNameTypeDescription]] = []
186
- diamond_model: Optional[list[DiamondModel]] = []
187
- recommended_queries: Optional[list[Query]] = []
188
- header_image: Optional[IdNameType] = None
182
+ attachment: str | None = None
183
+ events: list[NoteEvent] | None = []
184
+ validated_on: datetime | None = None
185
+ note_entities: list[NoteEntity] | None = []
186
+ context_entities: list[NoteEntity] | None = []
187
+ topic: list[NoteEntity] | NoteEntity | None = []
188
+ labels: list[NoteEntity] | None = []
189
+ validation_urls: list[NoteEntity] | None = []
190
+ diamond_model: list[DiamondModel] | None = []
191
+ recommended_queries: list[Query] | None = []
192
+ header_image: IdNameType | None = None
189
193
 
190
194
  @field_validator('events', mode='after')
191
195
  @classmethod
@@ -197,24 +201,24 @@ class Attributes(RFBaseModel):
197
201
  class PreviewAttributesIn(RFBaseModel):
198
202
  title: str
199
203
  text: str
200
- note_entities: Optional[list[str]] = []
201
- context_entities: Optional[list[str]] = []
204
+ note_entities: list[str] | None = []
205
+ context_entities: list[str] | None = []
202
206
  topic: Annotated[
203
- Union[list[str], str, None],
207
+ list[str] | str | None,
204
208
  BeforeValidator(Validators.convert_str_to_list),
205
209
  ] = []
206
- labels: Optional[list[str]] = []
207
- validation_urls: Optional[list[str]] = []
210
+ labels: list[str] | None = []
211
+ validation_urls: list[str] | None = []
208
212
 
209
213
 
210
214
  class PreviewAttributesOut(RFBaseModel):
211
215
  title: str
212
216
  text: str
213
- note_entities: Optional[list[IdNameTypeDescription]] = []
214
- context_entities: Optional[list[IdNameTypeDescription]] = []
215
- topic: Optional[list[IdNameTypeDescription]] = []
216
- labels: Optional[list[IdNameTypeDescription]] = []
217
- validation_urls: Optional[list[IdNameTypeDescription]] = []
217
+ note_entities: list[NoteEntity] | None = []
218
+ context_entities: list[NoteEntity] | None = []
219
+ topic: list[NoteEntity] | None = []
220
+ labels: list[NoteEntity] | None = []
221
+ validation_urls: list[NoteEntity] | None = []
218
222
 
219
223
 
220
224
  class RequestAttachment(RFBaseModel):
@@ -12,7 +12,7 @@
12
12
  ##############################################################################################
13
13
 
14
14
  from functools import total_ordering
15
- from typing import Annotated, Optional, Union
15
+ from typing import Annotated
16
16
 
17
17
  from pydantic import Field
18
18
  from typing_extensions import Doc
@@ -57,7 +57,7 @@ class AnalystNote(RFBaseModel):
57
57
  criterion.
58
58
  """
59
59
 
60
- external_id: Optional[str] = None
60
+ external_id: str | None = None
61
61
  source: IdNameTypeDescription
62
62
  attributes: Attributes
63
63
  id_: str = Field(alias='id')
@@ -78,7 +78,7 @@ class AnalystNote(RFBaseModel):
78
78
  )
79
79
 
80
80
  @property
81
- def detection_rule_type(self) -> Optional[str]:
81
+ def detection_rule_type(self) -> str | None:
82
82
  """Returns the attachment type if present, else None. It checks for specific types like
83
83
  `sigma rule`, `yara rule`, and `snort rule` in the topics of the note.
84
84
  """
@@ -113,7 +113,7 @@ class AnalystNote(RFBaseModel):
113
113
  bool, Doc('Defang URLs or other malicious indicators.')
114
114
  ] = False,
115
115
  character_limit: Annotated[
116
- Optional[int],
116
+ int | None,
117
117
  Doc('Limit the output to a specified number of characters.'),
118
118
  ] = None,
119
119
  ) -> Annotated[str, Doc('The generated markdown string.')]:
@@ -132,7 +132,7 @@ class AnalystNotePreviewIn(RFBaseModel):
132
132
  """Validate data sent to `/preview` endpoint."""
133
133
 
134
134
  attributes: PreviewAttributesIn
135
- source: Optional[str]
135
+ source: str | None
136
136
  tagged_text: bool = False
137
137
  serialization: str = 'full'
138
138
 
@@ -148,12 +148,12 @@ class AnalystNotePublishIn(AnalystNotePreviewIn):
148
148
  """Validate data sent to `/publish` endpoint."""
149
149
 
150
150
  attributes: PreviewAttributesIn
151
- source: Optional[str] = None
151
+ source: str | None = None
152
152
  tagged_text: bool = False
153
153
  serialization: str = 'full'
154
- note_id: Optional[str] = None
154
+ note_id: str | None = None
155
155
  resolve_entities: bool = True
156
- attachment_content_details: Optional[RequestAttachment] = None
156
+ attachment_content_details: RequestAttachment | None = None
157
157
 
158
158
 
159
159
  class AnalystNotePublishOut(RFBaseModel):
@@ -165,14 +165,14 @@ class AnalystNotePublishOut(RFBaseModel):
165
165
  class AnalystNoteSearchIn(RFBaseModel):
166
166
  """Validate data sent to `/search` endpoint."""
167
167
 
168
- published: Optional[str] = None
169
- entity: Optional[str] = None
170
- author: Optional[str] = None
171
- title: Optional[str] = None
172
- topic: Union[list[str], str, None] = []
173
- label: Optional[str] = None
174
- source: Optional[str] = None
168
+ published: str | None = None
169
+ entity: str | None = None
170
+ author: str | None = None
171
+ title: str | None = None
172
+ topic: list[str] | str | None = []
173
+ label: str | None = None
174
+ source: str | None = None
175
175
  serialization: str = None
176
176
  tagged_text: bool = None
177
177
  limit: int = NOTES_PER_PAGE
178
- from_: Optional[str] = Field(alias='from', default=None)
178
+ from_: str | None = Field(alias='from', default=None)
@@ -14,7 +14,7 @@
14
14
  import logging
15
15
  import re
16
16
  from itertools import chain
17
- from typing import Annotated, Optional, Union
17
+ from typing import Annotated
18
18
 
19
19
  from pydantic import Field, validate_call
20
20
  from typing_extensions import Doc
@@ -66,23 +66,23 @@ class AnalystNoteMgr:
66
66
  @validate_call
67
67
  def search(
68
68
  self,
69
- published: Annotated[Optional[str], Doc('Notes published after a date.')] = None,
70
- entity: Annotated[Optional[str], Doc('An entity the note refers to, RF ID.')] = None,
71
- author: Annotated[Optional[str], Doc('An author of the note, RF ID.')] = None,
72
- title: Annotated[Optional[str], Doc('A title of the note.')] = None,
73
- topic: Annotated[Optional[Union[str, list]], Doc('A topic of the note, RF ID.')] = None,
74
- label: Annotated[Optional[str], Doc('A label of the note, by name.')] = None,
75
- source: Annotated[Optional[str], Doc('The source of the note.')] = None,
69
+ published: Annotated[str | None, Doc('Notes published after a date.')] = None,
70
+ entity: Annotated[str | None, Doc('An entity the note refers to, RF ID.')] = None,
71
+ author: Annotated[str | None, Doc('An author of the note, RF ID.')] = None,
72
+ title: Annotated[str | None, Doc('A title of the note.')] = None,
73
+ topic: Annotated[str | list | None, Doc('A topic of the note, RF ID.')] = None,
74
+ label: Annotated[str | None, Doc('A label of the note, by name.')] = None,
75
+ source: Annotated[str | None, Doc('The source of the note.')] = None,
76
76
  serialization: Annotated[
77
- Optional[str], Doc('An entity serializer (id, min, full, raw).')
77
+ str | None, Doc('An entity serializer (id, min, full, raw).')
78
78
  ] = None,
79
- tagged_text: Annotated[Optional[bool], Doc('Should the text contain tags.')] = None,
79
+ tagged_text: Annotated[bool | None, Doc('Should the text contain tags.')] = None,
80
80
  max_results: Annotated[
81
- Optional[int],
81
+ int | None,
82
82
  Doc('The maximum number of references (not notes), max 1000.'),
83
83
  ] = Field(ge=1, le=1000, default=DEFAULT_LIMIT),
84
84
  notes_per_page: Annotated[
85
- Optional[int], Doc('The number of notes for each paged request.')
85
+ int | None, Doc('The number of notes for each paged request.')
86
86
  ] = Field(ge=1, le=1000, default=NOTES_PER_PAGE),
87
87
  ) -> Annotated[list[AnalystNote], Doc('A list of deduplicated AnalystNote objects.')]:
88
88
  """Execute a search for the analyst notes based on the parameters provided.
@@ -189,16 +189,16 @@ class AnalystNoteMgr:
189
189
  self,
190
190
  title: Annotated[str, Doc('The title of the note.')],
191
191
  text: Annotated[str, Doc('The text of the note.')],
192
- published: Annotated[Optional[str], Doc('The date when the note was published.')] = None,
193
- topic: Annotated[Union[str, list[str], None], Doc('The topic of the note.')] = None,
192
+ published: Annotated[str | None, Doc('The date when the note was published.')] = None,
193
+ topic: Annotated[str | list[str] | None, Doc('The topic of the note.')] = None,
194
194
  context_entities: Annotated[
195
- Optional[list[str]], Doc('The context entities of the note.')
195
+ list[str] | None, Doc('The context entities of the note.')
196
196
  ] = None,
197
- note_entities: Annotated[Optional[list[str]], Doc('The note entities of the note.')] = None,
197
+ note_entities: Annotated[list[str] | None, Doc('The note entities of the note.')] = None,
198
198
  validation_urls: Annotated[
199
- Optional[list[str]], Doc('The validation URLs of the note.')
199
+ list[str] | None, Doc('The validation URLs of the note.')
200
200
  ] = None,
201
- source: Annotated[Optional[str], Doc('The source of the note.')] = None,
201
+ source: Annotated[str | None, Doc('The source of the note.')] = None,
202
202
  ) -> Annotated[AnalystNotePreviewOut, Doc('The note that will be created.')]:
203
203
  """Preview of the AnalystNote. It does not create a note; it just returns how the note
204
204
  will look.
@@ -236,18 +236,18 @@ class AnalystNoteMgr:
236
236
  self,
237
237
  title: Annotated[str, Doc('The title of the note.')],
238
238
  text: Annotated[str, Doc('The text of the note.')],
239
- published: Annotated[Optional[str], Doc('The date when the note was published.')] = None,
240
- topic: Annotated[Union[str, list[str], None], Doc('The topic of the note.')] = None,
239
+ published: Annotated[str | None, Doc('The date when the note was published.')] = None,
240
+ topic: Annotated[str | list[str] | None, Doc('The topic of the note.')] = None,
241
241
  context_entities: Annotated[
242
- Optional[list[str]], Doc('The context entities of the note.')
242
+ list[str] | None, Doc('The context entities of the note.')
243
243
  ] = None,
244
- note_entities: Annotated[Optional[list[str]], Doc('The note entities of the note.')] = None,
244
+ note_entities: Annotated[list[str] | None, Doc('The note entities of the note.')] = None,
245
245
  validation_urls: Annotated[
246
- Optional[list[str]], Doc('The validation URLs of the note.')
246
+ list[str] | None, Doc('The validation URLs of the note.')
247
247
  ] = None,
248
- source: Annotated[Optional[str], Doc('The source of the note.')] = None,
248
+ source: Annotated[str | None, Doc('The source of the note.')] = None,
249
249
  note_id: Annotated[
250
- Optional[str], Doc('The ID of the note. Use if you want to modify an existing note.')
250
+ str | None, Doc('The ID of the note. Use if you want to modify an existing note.')
251
251
  ] = None,
252
252
  ) -> Annotated[AnalystNotePublishOut, Doc('The published note.')]:
253
253
  """Publish data. This method creates a note and returns its ID.