nat-engine 1__py3-none-any.whl

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 (299) hide show
  1. mannf/__init__.py +33 -0
  2. mannf/__main__.py +10 -0
  3. mannf/_version.py +8 -0
  4. mannf/agents/__init__.py +7 -0
  5. mannf/agents/analyzer_agent.py +9 -0
  6. mannf/agents/base.py +9 -0
  7. mannf/agents/bdi_agent.py +9 -0
  8. mannf/agents/belief_state.py +9 -0
  9. mannf/agents/coordinator_agent.py +9 -0
  10. mannf/agents/executor_agent.py +9 -0
  11. mannf/agents/monitor_agent.py +9 -0
  12. mannf/agents/oracle_agent.py +9 -0
  13. mannf/agents/planner_agent.py +9 -0
  14. mannf/agents/test_agent.py +9 -0
  15. mannf/anomaly/__init__.py +7 -0
  16. mannf/anomaly/enhanced_detector.py +9 -0
  17. mannf/cli.py +9 -0
  18. mannf/core/__init__.py +26 -0
  19. mannf/core/agents/__init__.py +52 -0
  20. mannf/core/agents/accessibility_scanner_agent.py +245 -0
  21. mannf/core/agents/analyzer_agent.py +224 -0
  22. mannf/core/agents/autonomous_loop_agent.py +1086 -0
  23. mannf/core/agents/autonomous_loop_models.py +62 -0
  24. mannf/core/agents/autonomous_run_differ.py +427 -0
  25. mannf/core/agents/base.py +128 -0
  26. mannf/core/agents/bdi_agent.py +330 -0
  27. mannf/core/agents/belief_state.py +202 -0
  28. mannf/core/agents/browser_coordinator_agent.py +224 -0
  29. mannf/core/agents/browser_executor_agent.py +410 -0
  30. mannf/core/agents/coordinator_agent.py +262 -0
  31. mannf/core/agents/executor_agent.py +222 -0
  32. mannf/core/agents/monitor_agent.py +188 -0
  33. mannf/core/agents/oracle_agent.py +150 -0
  34. mannf/core/agents/performance_testing_agent.py +279 -0
  35. mannf/core/agents/planner_agent.py +128 -0
  36. mannf/core/agents/test_agent.py +249 -0
  37. mannf/core/agents/visual_regression_agent.py +311 -0
  38. mannf/core/agents/web_crawler_agent.py +510 -0
  39. mannf/core/agents/worker_pool.py +366 -0
  40. mannf/core/anomaly/__init__.py +14 -0
  41. mannf/core/anomaly/enhanced_detector.py +541 -0
  42. mannf/core/browser/__init__.py +63 -0
  43. mannf/core/browser/accessibility_scanner.py +424 -0
  44. mannf/core/browser/discovery_model.py +178 -0
  45. mannf/core/browser/dom_snapshot.py +349 -0
  46. mannf/core/browser/ingestor_bridge.py +371 -0
  47. mannf/core/browser/performance_metrics.py +217 -0
  48. mannf/core/browser/reflection_analyzer.py +442 -0
  49. mannf/core/browser/scenario_generator.py +1100 -0
  50. mannf/core/browser/security_scenario_generator.py +695 -0
  51. mannf/core/browser/visual_comparer.py +159 -0
  52. mannf/core/diagnostics/__init__.py +28 -0
  53. mannf/core/diagnostics/failure_clusterer.py +211 -0
  54. mannf/core/diagnostics/flake_detector.py +233 -0
  55. mannf/core/diagnostics/root_cause_analyzer.py +273 -0
  56. mannf/core/distributed/__init__.py +16 -0
  57. mannf/core/distributed/endpoint.py +139 -0
  58. mannf/core/distributed/system_under_test.py +207 -0
  59. mannf/core/functional_orchestrator.py +428 -0
  60. mannf/core/messaging/__init__.py +11 -0
  61. mannf/core/messaging/bus.py +113 -0
  62. mannf/core/messaging/messages.py +89 -0
  63. mannf/core/nat_orchestrator.py +342 -0
  64. mannf/core/neural/__init__.py +183 -0
  65. mannf/core/orchestrator.py +272 -0
  66. mannf/core/prioritization/__init__.py +17 -0
  67. mannf/core/prioritization/adaptive_controller.py +509 -0
  68. mannf/core/prioritization/belief_prioritizer.py +231 -0
  69. mannf/core/prioritization/risk_scorer.py +430 -0
  70. mannf/core/reporting/__init__.py +12 -0
  71. mannf/core/reporting/unified_report.py +664 -0
  72. mannf/core/testing/__init__.py +17 -0
  73. mannf/core/testing/adaptive_controller.py +149 -0
  74. mannf/core/testing/models.py +179 -0
  75. mannf/core/validation/__init__.py +10 -0
  76. mannf/core/validation/self_validation_runner.py +180 -0
  77. mannf/dashboard/__init__.py +7 -0
  78. mannf/dashboard/app.py +9 -0
  79. mannf/dashboard/models.py +9 -0
  80. mannf/dashboard/static/index.html +2538 -0
  81. mannf/dashboard/telemetry.py +9 -0
  82. mannf/distributed/__init__.py +7 -0
  83. mannf/distributed/endpoint.py +9 -0
  84. mannf/distributed/system_under_test.py +9 -0
  85. mannf/healing/__init__.py +7 -0
  86. mannf/healing/graphql_schema_diff.py +9 -0
  87. mannf/healing/healer.py +9 -0
  88. mannf/healing/models.py +9 -0
  89. mannf/healing/schema_diff.py +9 -0
  90. mannf/integrations/__init__.py +7 -0
  91. mannf/integrations/auth.py +9 -0
  92. mannf/integrations/graphql_parser.py +9 -0
  93. mannf/integrations/graphql_sut.py +9 -0
  94. mannf/integrations/http_sut.py +9 -0
  95. mannf/integrations/openapi_parser.py +9 -0
  96. mannf/integrations/postman_parser.py +9 -0
  97. mannf/llm/__init__.py +7 -0
  98. mannf/llm/anthropic_provider.py +9 -0
  99. mannf/llm/base.py +9 -0
  100. mannf/llm/config.py +9 -0
  101. mannf/llm/factory.py +9 -0
  102. mannf/llm/openai_provider.py +9 -0
  103. mannf/llm/prompts.py +9 -0
  104. mannf/messaging/__init__.py +7 -0
  105. mannf/messaging/bus.py +9 -0
  106. mannf/messaging/messages.py +9 -0
  107. mannf/nat_orchestrator.py +9 -0
  108. mannf/neural/__init__.py +7 -0
  109. mannf/orchestrator.py +9 -0
  110. mannf/prioritization/__init__.py +7 -0
  111. mannf/prioritization/adaptive_controller.py +9 -0
  112. mannf/prioritization/belief_prioritizer.py +9 -0
  113. mannf/prioritization/risk_scorer.py +9 -0
  114. mannf/product/__init__.py +29 -0
  115. mannf/product/admin/__init__.py +3 -0
  116. mannf/product/admin/routes.py +514 -0
  117. mannf/product/auth/__init__.py +5 -0
  118. mannf/product/auth/saml.py +212 -0
  119. mannf/product/billing/__init__.py +5 -0
  120. mannf/product/billing/audit.py +160 -0
  121. mannf/product/billing/feature_gates.py +180 -0
  122. mannf/product/billing/metering.py +179 -0
  123. mannf/product/billing/notifications.py +181 -0
  124. mannf/product/billing/plans.py +133 -0
  125. mannf/product/billing/rate_limits.py +35 -0
  126. mannf/product/billing/stripe_billing.py +906 -0
  127. mannf/product/billing/tenant_auth.py +233 -0
  128. mannf/product/billing/tenant_manager.py +873 -0
  129. mannf/product/cli.py +3900 -0
  130. mannf/product/cli_admin.py +408 -0
  131. mannf/product/dashboard/__init__.py +61 -0
  132. mannf/product/dashboard/app.py +3567 -0
  133. mannf/product/dashboard/models.py +460 -0
  134. mannf/product/dashboard/static/index.html +6347 -0
  135. mannf/product/dashboard/static/manifest.json +25 -0
  136. mannf/product/dashboard/static/pwa-icon-192.png +0 -0
  137. mannf/product/dashboard/static/pwa-icon-512.png +0 -0
  138. mannf/product/dashboard/static/sw.js +64 -0
  139. mannf/product/dashboard/telemetry.py +547 -0
  140. mannf/product/database.py +145 -0
  141. mannf/product/demo.py +844 -0
  142. mannf/product/doctor.py +509 -0
  143. mannf/product/exporters/__init__.py +65 -0
  144. mannf/product/exporters/azuredevops_exporter.py +257 -0
  145. mannf/product/exporters/base.py +307 -0
  146. mannf/product/exporters/bugzilla_exporter.py +200 -0
  147. mannf/product/exporters/dedup.py +275 -0
  148. mannf/product/exporters/finding_adapter.py +216 -0
  149. mannf/product/exporters/github_exporter.py +197 -0
  150. mannf/product/exporters/gitlab_exporter.py +215 -0
  151. mannf/product/exporters/jira_exporter.py +180 -0
  152. mannf/product/exporters/linear_exporter.py +195 -0
  153. mannf/product/exporters/loader.py +233 -0
  154. mannf/product/exporters/pagerduty_exporter.py +363 -0
  155. mannf/product/exporters/sentry_exporter.py +322 -0
  156. mannf/product/exporters/servicenow_exporter.py +240 -0
  157. mannf/product/exporters/shortcut_exporter.py +231 -0
  158. mannf/product/exporters/webhook_exporter.py +383 -0
  159. mannf/product/formatters/__init__.py +18 -0
  160. mannf/product/formatters/allure_formatter.py +161 -0
  161. mannf/product/formatters/ctrf_formatter.py +149 -0
  162. mannf/product/healing/__init__.py +30 -0
  163. mannf/product/healing/graphql_schema_diff.py +152 -0
  164. mannf/product/healing/healer.py +141 -0
  165. mannf/product/healing/models.py +175 -0
  166. mannf/product/healing/schema_diff.py +251 -0
  167. mannf/product/ingestors/__init__.py +77 -0
  168. mannf/product/ingestors/base.py +256 -0
  169. mannf/product/ingestors/bgstm_ingestor.py +764 -0
  170. mannf/product/ingestors/curl_ingestor.py +1019 -0
  171. mannf/product/ingestors/cypress_ingestor.py +487 -0
  172. mannf/product/ingestors/gherkin_ingestor.py +967 -0
  173. mannf/product/ingestors/graphql_ingestor.py +845 -0
  174. mannf/product/ingestors/grpc_ingestor.py +591 -0
  175. mannf/product/ingestors/har_ingestor.py +976 -0
  176. mannf/product/ingestors/loader.py +284 -0
  177. mannf/product/ingestors/models.py +146 -0
  178. mannf/product/ingestors/openapi_ingestor.py +606 -0
  179. mannf/product/ingestors/playwright_ingestor.py +449 -0
  180. mannf/product/ingestors/postman_ingestor.py +631 -0
  181. mannf/product/ingestors/traffic_ingestor.py +679 -0
  182. mannf/product/ingestors/websocket_ingestor.py +526 -0
  183. mannf/product/integrations/__init__.py +21 -0
  184. mannf/product/integrations/auth.py +190 -0
  185. mannf/product/integrations/graphql_parser.py +436 -0
  186. mannf/product/integrations/graphql_sut.py +247 -0
  187. mannf/product/integrations/grpc_sut.py +469 -0
  188. mannf/product/integrations/http_sut.py +237 -0
  189. mannf/product/integrations/kafka_adapter.py +342 -0
  190. mannf/product/integrations/openapi_parser.py +513 -0
  191. mannf/product/integrations/postman_parser.py +467 -0
  192. mannf/product/integrations/webhook_receiver.py +344 -0
  193. mannf/product/integrations/websocket_sut.py +434 -0
  194. mannf/product/llm/__init__.py +25 -0
  195. mannf/product/llm/anthropic_provider.py +94 -0
  196. mannf/product/llm/base.py +267 -0
  197. mannf/product/llm/config.py +48 -0
  198. mannf/product/llm/factory.py +42 -0
  199. mannf/product/llm/openai_provider.py +93 -0
  200. mannf/product/llm/prompts.py +403 -0
  201. mannf/product/llm/root_cause_service.py +311 -0
  202. mannf/product/llm/test_plan_models.py +78 -0
  203. mannf/product/metrics.py +149 -0
  204. mannf/product/middleware/__init__.py +3 -0
  205. mannf/product/middleware/audit_middleware.py +112 -0
  206. mannf/product/middleware/tenant_isolation.py +114 -0
  207. mannf/product/models.py +347 -0
  208. mannf/product/notifications/__init__.py +24 -0
  209. mannf/product/notifications/dispatcher.py +411 -0
  210. mannf/product/onboarding.py +190 -0
  211. mannf/product/orchestration/__init__.py +39 -0
  212. mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
  213. mannf/product/orchestration/pipeline.py +401 -0
  214. mannf/product/orchestrator.py +987 -0
  215. mannf/product/orchestrator_models.py +269 -0
  216. mannf/product/regression/__init__.py +36 -0
  217. mannf/product/regression/differ.py +172 -0
  218. mannf/product/regression/masking.py +100 -0
  219. mannf/product/regression/models.py +232 -0
  220. mannf/product/regression/recorder.py +124 -0
  221. mannf/product/regression/replayer.py +168 -0
  222. mannf/product/reports/__init__.py +10 -0
  223. mannf/product/reports/pdf.py +132 -0
  224. mannf/product/scheduling/__init__.py +57 -0
  225. mannf/product/scheduling/cron_utils.py +251 -0
  226. mannf/product/scheduling/engine.py +473 -0
  227. mannf/product/scheduling/models.py +86 -0
  228. mannf/product/scheduling/queue.py +894 -0
  229. mannf/product/scheduling/store.py +235 -0
  230. mannf/product/security/__init__.py +21 -0
  231. mannf/product/security/belief_guided.py +143 -0
  232. mannf/product/security/checks/__init__.py +55 -0
  233. mannf/product/security/checks/base.py +69 -0
  234. mannf/product/security/checks/bfla.py +77 -0
  235. mannf/product/security/checks/bola.py +77 -0
  236. mannf/product/security/checks/bopla.py +80 -0
  237. mannf/product/security/checks/broken_auth.py +86 -0
  238. mannf/product/security/checks/graphql_security.py +299 -0
  239. mannf/product/security/checks/inventory.py +70 -0
  240. mannf/product/security/checks/misconfig.py +158 -0
  241. mannf/product/security/checks/resource_consumption.py +70 -0
  242. mannf/product/security/checks/sensitive_flows.py +80 -0
  243. mannf/product/security/checks/ssrf.py +101 -0
  244. mannf/product/security/checks/unsafe_consumption.py +120 -0
  245. mannf/product/security/models.py +92 -0
  246. mannf/product/security/plugin_loader.py +182 -0
  247. mannf/product/security/reporter.py +92 -0
  248. mannf/product/security/scanner.py +183 -0
  249. mannf/product/server.py +6220 -0
  250. mannf/product/setup_wizard.py +873 -0
  251. mannf/product/status.py +404 -0
  252. mannf/product/storage/__init__.py +10 -0
  253. mannf/product/storage/artifact_store.py +343 -0
  254. mannf/product/telemetry.py +300 -0
  255. mannf/product/uninstall.py +169 -0
  256. mannf/product/upgrade.py +139 -0
  257. mannf/product/weights/__init__.py +13 -0
  258. mannf/product/weights/blob_store.py +299 -0
  259. mannf/product/weights/factory.py +42 -0
  260. mannf/product/weights/registry.py +159 -0
  261. mannf/product/weights/store.py +210 -0
  262. mannf/regression/__init__.py +7 -0
  263. mannf/regression/differ.py +9 -0
  264. mannf/regression/masking.py +9 -0
  265. mannf/regression/models.py +9 -0
  266. mannf/regression/recorder.py +9 -0
  267. mannf/regression/replayer.py +9 -0
  268. mannf/security/__init__.py +7 -0
  269. mannf/security/belief_guided.py +9 -0
  270. mannf/security/checks/__init__.py +7 -0
  271. mannf/security/checks/base.py +9 -0
  272. mannf/security/checks/bfla.py +9 -0
  273. mannf/security/checks/bola.py +9 -0
  274. mannf/security/checks/bopla.py +9 -0
  275. mannf/security/checks/broken_auth.py +9 -0
  276. mannf/security/checks/graphql_security.py +9 -0
  277. mannf/security/checks/inventory.py +9 -0
  278. mannf/security/checks/misconfig.py +9 -0
  279. mannf/security/checks/resource_consumption.py +9 -0
  280. mannf/security/checks/sensitive_flows.py +9 -0
  281. mannf/security/checks/ssrf.py +9 -0
  282. mannf/security/checks/unsafe_consumption.py +9 -0
  283. mannf/security/models.py +9 -0
  284. mannf/security/reporter.py +9 -0
  285. mannf/security/scanner.py +9 -0
  286. mannf/server.py +9 -0
  287. mannf/testing/__init__.py +7 -0
  288. mannf/testing/adaptive_controller.py +9 -0
  289. mannf/testing/models.py +9 -0
  290. mannf/weights/__init__.py +7 -0
  291. mannf/weights/registry.py +9 -0
  292. mannf/weights/store.py +9 -0
  293. nat_engine-1.dist-info/METADATA +555 -0
  294. nat_engine-1.dist-info/RECORD +299 -0
  295. nat_engine-1.dist-info/WHEEL +5 -0
  296. nat_engine-1.dist-info/entry_points.txt +4 -0
  297. nat_engine-1.dist-info/licenses/LICENSE +651 -0
  298. nat_engine-1.dist-info/licenses/NOTICE +178 -0
  299. nat_engine-1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,284 @@
1
+ # Copyright (C) 2026 Brad Guider
2
+ # This file is part of NAT (Neural Agent Testing Framework).
3
+ # Licensed under the AGPL-3.0. See LICENSE for details.
4
+ # Commercial licensing available — see COMMERCIAL_LICENSE.md.
5
+
6
+ """Plugin loader for custom IngestorPlugin implementations.
7
+
8
+ Provides two discovery mechanisms:
9
+ - Directory scanning: load .py files from a directory path.
10
+ - Entry-point discovery: use importlib.metadata to find registered ingestors.
11
+
12
+ Both mechanisms validate that each discovered class has the required attributes
13
+ and callable methods before instantiating it. Invalid ingestors are skipped
14
+ with a warning — they will never cause the scanner to crash.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import importlib
20
+ import importlib.util
21
+ import inspect
22
+ import logging
23
+ import sys
24
+ from importlib.metadata import entry_points
25
+ from pathlib import Path
26
+
27
+ from mannf.product.ingestors.base import IngestorPlugin
28
+ from mannf.product.ingestors.models import IngestorSource
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ _REQUIRED_ATTRS = ("name", "display_name", "description", "supported_extensions", "supported_formats")
33
+
34
+
35
+ def _is_valid_ingestor(cls: type) -> bool:
36
+ """Return True if *cls* is a valid, concrete IngestorPlugin subclass."""
37
+ if not (isinstance(cls, type) and issubclass(cls, IngestorPlugin) and cls is not IngestorPlugin):
38
+ return False
39
+ for attr in _REQUIRED_ATTRS:
40
+ if not hasattr(cls, attr):
41
+ logger.warning(
42
+ "Ingestor %s is missing required attribute '%s' — skipping.",
43
+ cls.__name__,
44
+ attr,
45
+ )
46
+ return False
47
+ if not callable(getattr(cls, "ingest", None)):
48
+ logger.warning(
49
+ "Ingestor %s has no callable 'ingest' method — skipping.",
50
+ cls.__name__,
51
+ )
52
+ return False
53
+ if not callable(getattr(cls, "validate_config", None)):
54
+ logger.warning(
55
+ "Ingestor %s has no callable 'validate_config' method — skipping.",
56
+ cls.__name__,
57
+ )
58
+ return False
59
+ if not callable(getattr(cls, "can_handle", None)):
60
+ logger.warning(
61
+ "Ingestor %s has no callable 'can_handle' method — skipping.",
62
+ cls.__name__,
63
+ )
64
+ return False
65
+ return True
66
+
67
+
68
+ def _instantiate(cls: type) -> IngestorPlugin | None:
69
+ """Try to instantiate *cls*; log a warning and return None on failure."""
70
+ try:
71
+ return cls()
72
+ except Exception as exc: # noqa: BLE001
73
+ logger.warning(
74
+ "Ingestor %s raised %s during __init__ — skipping.", cls.__name__, exc
75
+ )
76
+ return None
77
+
78
+
79
+ def load_ingestors_from_directory(directory: Path) -> list[IngestorPlugin]:
80
+ """Discover and load IngestorPlugin subclasses from all ``.py`` files in *directory*.
81
+
82
+ Parameters
83
+ ----------
84
+ directory:
85
+ Path to a directory containing ingestor ``.py`` files.
86
+
87
+ Returns
88
+ -------
89
+ list[IngestorPlugin]
90
+ Instantiated ingestor objects for every valid ingestor found.
91
+ """
92
+ ingestors: list[IngestorPlugin] = []
93
+
94
+ if not directory.exists():
95
+ logger.warning(
96
+ "Ingestor directory '%s' does not exist — no ingestors loaded.", directory
97
+ )
98
+ return ingestors
99
+
100
+ if not directory.is_dir():
101
+ logger.warning(
102
+ "Ingestor path '%s' is not a directory — no ingestors loaded.", directory
103
+ )
104
+ return ingestors
105
+
106
+ for py_file in sorted(directory.glob("*.py")):
107
+ module_name = f"_nat_ingestor_{py_file.stem}"
108
+ try:
109
+ spec = importlib.util.spec_from_file_location(module_name, py_file)
110
+ if spec is None or spec.loader is None:
111
+ logger.warning(
112
+ "Could not create module spec for '%s' — skipping.", py_file
113
+ )
114
+ continue
115
+ module = importlib.util.module_from_spec(spec)
116
+ sys.modules[module_name] = module
117
+ spec.loader.exec_module(module) # type: ignore[union-attr]
118
+ except Exception as exc: # noqa: BLE001
119
+ logger.warning(
120
+ "Failed to import ingestor file '%s': %s — skipping.", py_file, exc
121
+ )
122
+ continue
123
+
124
+ for _name, obj in inspect.getmembers(module, inspect.isclass):
125
+ if obj.__module__ != module_name:
126
+ # Avoid re-processing imported base classes
127
+ continue
128
+ if _is_valid_ingestor(obj):
129
+ instance = _instantiate(obj)
130
+ if instance is not None:
131
+ ingestors.append(instance)
132
+
133
+ return ingestors
134
+
135
+
136
+ def load_ingestors_from_entry_points(
137
+ group: str = "nat.ingestors",
138
+ ) -> list[IngestorPlugin]:
139
+ """Discover and load IngestorPlugin subclasses registered as package entry points.
140
+
141
+ Parameters
142
+ ----------
143
+ group:
144
+ The entry-point group name to scan (default ``"nat.ingestors"``).
145
+
146
+ Returns
147
+ -------
148
+ list[IngestorPlugin]
149
+ Instantiated ingestor objects for every valid entry point found.
150
+ """
151
+ ingestors: list[IngestorPlugin] = []
152
+
153
+ try:
154
+ eps = entry_points(group=group)
155
+ except Exception as exc: # noqa: BLE001
156
+ logger.warning(
157
+ "Failed to query entry points for group '%s': %s", group, exc
158
+ )
159
+ return ingestors
160
+
161
+ for ep in eps:
162
+ try:
163
+ obj = ep.load()
164
+ except Exception as exc: # noqa: BLE001
165
+ logger.warning(
166
+ "Failed to load entry point '%s': %s — skipping.", ep.name, exc
167
+ )
168
+ continue
169
+
170
+ if _is_valid_ingestor(obj):
171
+ instance = _instantiate(obj)
172
+ if instance is not None:
173
+ ingestors.append(instance)
174
+ else:
175
+ logger.warning(
176
+ "Entry point '%s' does not point to a valid IngestorPlugin subclass — skipping.",
177
+ ep.name,
178
+ )
179
+
180
+ return ingestors
181
+
182
+
183
+ def load_all_ingestors(
184
+ ingestors_dir: Path | None = None,
185
+ entry_point_group: str = "nat.ingestors",
186
+ ) -> list[IngestorPlugin]:
187
+ """Load ingestors from built-ins, a directory, and registered entry points.
188
+
189
+ Results are deduplicated by class name; built-in ingestors take precedence.
190
+
191
+ Parameters
192
+ ----------
193
+ ingestors_dir:
194
+ Optional path to a directory of ingestor ``.py`` files.
195
+ entry_point_group:
196
+ Entry-point group to scan (default ``"nat.ingestors"``).
197
+
198
+ Returns
199
+ -------
200
+ list[IngestorPlugin]
201
+ Merged, deduplicated list of instantiated ingestor objects.
202
+ """
203
+ from mannf.product.ingestors import BUILTIN_INGESTORS # avoid circular import
204
+
205
+ seen_class_names: set[str] = set()
206
+ combined: list[IngestorPlugin] = []
207
+
208
+ dir_ingestors = (
209
+ load_ingestors_from_directory(ingestors_dir) if ingestors_dir else []
210
+ )
211
+ ep_ingestors = load_ingestors_from_entry_points(entry_point_group)
212
+
213
+ for ingestor in BUILTIN_INGESTORS + dir_ingestors + ep_ingestors:
214
+ class_name = type(ingestor).__name__
215
+ if class_name not in seen_class_names:
216
+ seen_class_names.add(class_name)
217
+ combined.append(ingestor)
218
+
219
+ return combined
220
+
221
+
222
+ def get_ingestor_by_name(
223
+ name: str,
224
+ ingestors: list[IngestorPlugin] | None = None,
225
+ ) -> IngestorPlugin | None:
226
+ """Look up an ingestor by its ``name`` attribute.
227
+
228
+ Parameters
229
+ ----------
230
+ name:
231
+ The ingestor name to search for (e.g., ``"openapi"``, ``"postman"``).
232
+ ingestors:
233
+ List of instantiated ingestor objects to search. Defaults to
234
+ :func:`load_all_ingestors` if not provided.
235
+
236
+ Returns
237
+ -------
238
+ IngestorPlugin | None
239
+ The first matching ingestor, or ``None`` if not found.
240
+ """
241
+ if ingestors is None:
242
+ ingestors = load_all_ingestors()
243
+ for ingestor in ingestors:
244
+ if ingestor.name == name:
245
+ return ingestor
246
+ return None
247
+
248
+
249
+ def get_ingestor_for_source(
250
+ source: IngestorSource,
251
+ ingestors: list[IngestorPlugin] | None = None,
252
+ ) -> IngestorPlugin | None:
253
+ """Auto-detect and return the appropriate ingestor for *source*.
254
+
255
+ Iterates through the available ingestors and returns the first one whose
256
+ :meth:`~IngestorPlugin.can_handle` method returns True.
257
+
258
+ Parameters
259
+ ----------
260
+ source:
261
+ The source to find an ingestor for.
262
+ ingestors:
263
+ List of instantiated ingestor objects to search. Defaults to
264
+ :func:`load_all_ingestors` if not provided.
265
+
266
+ Returns
267
+ -------
268
+ IngestorPlugin | None
269
+ The first matching ingestor, or ``None`` if no ingestor can handle
270
+ the source.
271
+ """
272
+ if ingestors is None:
273
+ ingestors = load_all_ingestors()
274
+ for ingestor in ingestors:
275
+ try:
276
+ if ingestor.can_handle(source):
277
+ return ingestor
278
+ except Exception as exc: # noqa: BLE001
279
+ logger.warning(
280
+ "Ingestor '%s' raised an error in can_handle: %s — skipping.",
281
+ ingestor.name,
282
+ exc,
283
+ )
284
+ return None
@@ -0,0 +1,146 @@
1
+ # Copyright (C) 2026 Brad Guider
2
+ # This file is part of NAT (Neural Agent Testing Framework).
3
+ # Licensed under the AGPL-3.0. See LICENSE for details.
4
+ # Commercial licensing available — see COMMERCIAL_LICENSE.md.
5
+
6
+ """Dataclass models for the ingestor plugin architecture.
7
+
8
+ These models represent the unified internal format that all ingestor plugins
9
+ produce when consuming specs, test cases, and other artifacts.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+
16
+
17
+ @dataclass
18
+ class IngestorSource:
19
+ """Metadata about the source being ingested.
20
+
21
+ Attributes
22
+ ----------
23
+ format:
24
+ Source format identifier (e.g., ``"openapi"``, ``"postman"``,
25
+ ``"har"``, ``"graphql"``, ``"gherkin"``, ``"bgstm"``, ``"curl"``).
26
+ path:
27
+ File path if the source is a local file.
28
+ url:
29
+ URL if the source is a remote resource.
30
+ raw_content:
31
+ Raw content string if provided inline.
32
+ metadata:
33
+ Arbitrary metadata from the source.
34
+ """
35
+
36
+ format: str
37
+ path: str | None = None
38
+ url: str | None = None
39
+ raw_content: str | None = None
40
+ metadata: dict = field(default_factory=dict)
41
+
42
+
43
+ @dataclass
44
+ class IngestedEndpoint:
45
+ """A single API endpoint/operation extracted from an ingested source.
46
+
47
+ Attributes
48
+ ----------
49
+ method:
50
+ HTTP method (GET, POST, PUT, DELETE, etc.).
51
+ path:
52
+ URL path template (e.g., ``"/api/v1/users/{id}"``).
53
+ summary:
54
+ Human-readable summary of the operation.
55
+ description:
56
+ Detailed description of the operation.
57
+ parameters:
58
+ Parameter definitions (query, path, header, cookie).
59
+ request_body:
60
+ Request body schema or example, if applicable.
61
+ responses:
62
+ Expected response schemas keyed by HTTP status code.
63
+ auth_requirements:
64
+ Authentication schemes required for this endpoint.
65
+ tags:
66
+ Categorization tags.
67
+ metadata:
68
+ Format-specific extra data.
69
+ """
70
+
71
+ method: str
72
+ path: str
73
+ summary: str = ""
74
+ description: str = ""
75
+ parameters: list[dict] = field(default_factory=list)
76
+ request_body: dict | None = None
77
+ responses: dict = field(default_factory=dict)
78
+ auth_requirements: list[str] = field(default_factory=list)
79
+ tags: list[str] = field(default_factory=list)
80
+ metadata: dict = field(default_factory=dict)
81
+
82
+
83
+ @dataclass
84
+ class IngestedTestCase:
85
+ """A test case extracted from an ingested source (e.g., BDD/Gherkin/BGSTM).
86
+
87
+ Attributes
88
+ ----------
89
+ name:
90
+ Test case name.
91
+ description:
92
+ Test case description.
93
+ steps:
94
+ Ordered list of test steps.
95
+ expected_result:
96
+ Expected outcome of the test.
97
+ priority:
98
+ Priority level (``"critical"``, ``"high"``, ``"medium"``, ``"low"``).
99
+ tags:
100
+ Categorization tags.
101
+ source_ref:
102
+ Reference back to the source (file:line, scenario name, etc.).
103
+ metadata:
104
+ Format-specific extra data.
105
+ """
106
+
107
+ name: str
108
+ description: str = ""
109
+ steps: list[dict] = field(default_factory=list)
110
+ expected_result: str = ""
111
+ priority: str = "medium"
112
+ tags: list[str] = field(default_factory=list)
113
+ source_ref: str = ""
114
+ metadata: dict = field(default_factory=dict)
115
+
116
+
117
+ @dataclass
118
+ class IngestResult:
119
+ """The complete output of an ingestion operation.
120
+
121
+ Attributes
122
+ ----------
123
+ success:
124
+ Whether ingestion succeeded.
125
+ source:
126
+ The source that was ingested.
127
+ endpoints:
128
+ Extracted API endpoints.
129
+ test_cases:
130
+ Extracted test cases.
131
+ errors:
132
+ Any errors encountered during ingestion.
133
+ warnings:
134
+ Any warnings (non-fatal issues).
135
+ stats:
136
+ Summary statistics (e.g., ``endpoint_count``, ``test_case_count``,
137
+ ``duration_ms``).
138
+ """
139
+
140
+ success: bool
141
+ source: IngestorSource
142
+ endpoints: list[IngestedEndpoint] = field(default_factory=list)
143
+ test_cases: list[IngestedTestCase] = field(default_factory=list)
144
+ errors: list[str] = field(default_factory=list)
145
+ warnings: list[str] = field(default_factory=list)
146
+ stats: dict = field(default_factory=dict)