truthound 1.0.8__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 (877) hide show
  1. truthound/__init__.py +162 -0
  2. truthound/adapters.py +100 -0
  3. truthound/api.py +365 -0
  4. truthound/audit/__init__.py +248 -0
  5. truthound/audit/core.py +967 -0
  6. truthound/audit/filters.py +620 -0
  7. truthound/audit/formatters.py +707 -0
  8. truthound/audit/logger.py +902 -0
  9. truthound/audit/middleware.py +571 -0
  10. truthound/audit/storage.py +1083 -0
  11. truthound/benchmark/__init__.py +123 -0
  12. truthound/benchmark/base.py +757 -0
  13. truthound/benchmark/comparison.py +635 -0
  14. truthound/benchmark/generators.py +706 -0
  15. truthound/benchmark/reporters.py +718 -0
  16. truthound/benchmark/runner.py +635 -0
  17. truthound/benchmark/scenarios.py +712 -0
  18. truthound/cache.py +252 -0
  19. truthound/checkpoint/__init__.py +136 -0
  20. truthound/checkpoint/actions/__init__.py +164 -0
  21. truthound/checkpoint/actions/base.py +324 -0
  22. truthound/checkpoint/actions/custom.py +234 -0
  23. truthound/checkpoint/actions/discord_notify.py +290 -0
  24. truthound/checkpoint/actions/email_notify.py +405 -0
  25. truthound/checkpoint/actions/github_action.py +406 -0
  26. truthound/checkpoint/actions/opsgenie.py +1499 -0
  27. truthound/checkpoint/actions/pagerduty.py +226 -0
  28. truthound/checkpoint/actions/slack_notify.py +233 -0
  29. truthound/checkpoint/actions/store_result.py +249 -0
  30. truthound/checkpoint/actions/teams_notify.py +1570 -0
  31. truthound/checkpoint/actions/telegram_notify.py +419 -0
  32. truthound/checkpoint/actions/update_docs.py +552 -0
  33. truthound/checkpoint/actions/webhook.py +293 -0
  34. truthound/checkpoint/analytics/__init__.py +147 -0
  35. truthound/checkpoint/analytics/aggregations/__init__.py +23 -0
  36. truthound/checkpoint/analytics/aggregations/rollup.py +481 -0
  37. truthound/checkpoint/analytics/aggregations/time_bucket.py +306 -0
  38. truthound/checkpoint/analytics/analyzers/__init__.py +17 -0
  39. truthound/checkpoint/analytics/analyzers/anomaly.py +386 -0
  40. truthound/checkpoint/analytics/analyzers/base.py +270 -0
  41. truthound/checkpoint/analytics/analyzers/forecast.py +421 -0
  42. truthound/checkpoint/analytics/analyzers/trend.py +314 -0
  43. truthound/checkpoint/analytics/models.py +292 -0
  44. truthound/checkpoint/analytics/protocols.py +549 -0
  45. truthound/checkpoint/analytics/service.py +718 -0
  46. truthound/checkpoint/analytics/stores/__init__.py +16 -0
  47. truthound/checkpoint/analytics/stores/base.py +306 -0
  48. truthound/checkpoint/analytics/stores/memory_store.py +353 -0
  49. truthound/checkpoint/analytics/stores/sqlite_store.py +557 -0
  50. truthound/checkpoint/analytics/stores/timescale_store.py +501 -0
  51. truthound/checkpoint/async_actions.py +794 -0
  52. truthound/checkpoint/async_base.py +708 -0
  53. truthound/checkpoint/async_checkpoint.py +617 -0
  54. truthound/checkpoint/async_runner.py +639 -0
  55. truthound/checkpoint/checkpoint.py +527 -0
  56. truthound/checkpoint/ci/__init__.py +61 -0
  57. truthound/checkpoint/ci/detector.py +355 -0
  58. truthound/checkpoint/ci/reporter.py +436 -0
  59. truthound/checkpoint/ci/templates.py +454 -0
  60. truthound/checkpoint/circuitbreaker/__init__.py +133 -0
  61. truthound/checkpoint/circuitbreaker/breaker.py +542 -0
  62. truthound/checkpoint/circuitbreaker/core.py +252 -0
  63. truthound/checkpoint/circuitbreaker/detection.py +459 -0
  64. truthound/checkpoint/circuitbreaker/middleware.py +389 -0
  65. truthound/checkpoint/circuitbreaker/registry.py +357 -0
  66. truthound/checkpoint/distributed/__init__.py +139 -0
  67. truthound/checkpoint/distributed/backends/__init__.py +35 -0
  68. truthound/checkpoint/distributed/backends/celery_backend.py +503 -0
  69. truthound/checkpoint/distributed/backends/kubernetes_backend.py +696 -0
  70. truthound/checkpoint/distributed/backends/local_backend.py +397 -0
  71. truthound/checkpoint/distributed/backends/ray_backend.py +625 -0
  72. truthound/checkpoint/distributed/base.py +774 -0
  73. truthound/checkpoint/distributed/orchestrator.py +765 -0
  74. truthound/checkpoint/distributed/protocols.py +842 -0
  75. truthound/checkpoint/distributed/registry.py +449 -0
  76. truthound/checkpoint/idempotency/__init__.py +120 -0
  77. truthound/checkpoint/idempotency/core.py +295 -0
  78. truthound/checkpoint/idempotency/fingerprint.py +454 -0
  79. truthound/checkpoint/idempotency/locking.py +604 -0
  80. truthound/checkpoint/idempotency/service.py +592 -0
  81. truthound/checkpoint/idempotency/stores.py +653 -0
  82. truthound/checkpoint/monitoring/__init__.py +134 -0
  83. truthound/checkpoint/monitoring/aggregators/__init__.py +15 -0
  84. truthound/checkpoint/monitoring/aggregators/base.py +372 -0
  85. truthound/checkpoint/monitoring/aggregators/realtime.py +300 -0
  86. truthound/checkpoint/monitoring/aggregators/window.py +493 -0
  87. truthound/checkpoint/monitoring/collectors/__init__.py +17 -0
  88. truthound/checkpoint/monitoring/collectors/base.py +257 -0
  89. truthound/checkpoint/monitoring/collectors/memory_collector.py +617 -0
  90. truthound/checkpoint/monitoring/collectors/prometheus_collector.py +451 -0
  91. truthound/checkpoint/monitoring/collectors/redis_collector.py +518 -0
  92. truthound/checkpoint/monitoring/events.py +410 -0
  93. truthound/checkpoint/monitoring/protocols.py +636 -0
  94. truthound/checkpoint/monitoring/service.py +578 -0
  95. truthound/checkpoint/monitoring/views/__init__.py +17 -0
  96. truthound/checkpoint/monitoring/views/base.py +172 -0
  97. truthound/checkpoint/monitoring/views/queue_view.py +220 -0
  98. truthound/checkpoint/monitoring/views/task_view.py +240 -0
  99. truthound/checkpoint/monitoring/views/worker_view.py +263 -0
  100. truthound/checkpoint/registry.py +337 -0
  101. truthound/checkpoint/runner.py +356 -0
  102. truthound/checkpoint/transaction/__init__.py +133 -0
  103. truthound/checkpoint/transaction/base.py +389 -0
  104. truthound/checkpoint/transaction/compensatable.py +537 -0
  105. truthound/checkpoint/transaction/coordinator.py +576 -0
  106. truthound/checkpoint/transaction/executor.py +622 -0
  107. truthound/checkpoint/transaction/idempotency.py +534 -0
  108. truthound/checkpoint/transaction/saga/__init__.py +143 -0
  109. truthound/checkpoint/transaction/saga/builder.py +584 -0
  110. truthound/checkpoint/transaction/saga/definition.py +515 -0
  111. truthound/checkpoint/transaction/saga/event_store.py +542 -0
  112. truthound/checkpoint/transaction/saga/patterns.py +833 -0
  113. truthound/checkpoint/transaction/saga/runner.py +718 -0
  114. truthound/checkpoint/transaction/saga/state_machine.py +793 -0
  115. truthound/checkpoint/transaction/saga/strategies.py +780 -0
  116. truthound/checkpoint/transaction/saga/testing.py +886 -0
  117. truthound/checkpoint/triggers/__init__.py +58 -0
  118. truthound/checkpoint/triggers/base.py +237 -0
  119. truthound/checkpoint/triggers/event.py +385 -0
  120. truthound/checkpoint/triggers/schedule.py +355 -0
  121. truthound/cli.py +2358 -0
  122. truthound/cli_modules/__init__.py +124 -0
  123. truthound/cli_modules/advanced/__init__.py +45 -0
  124. truthound/cli_modules/advanced/benchmark.py +343 -0
  125. truthound/cli_modules/advanced/docs.py +225 -0
  126. truthound/cli_modules/advanced/lineage.py +209 -0
  127. truthound/cli_modules/advanced/ml.py +320 -0
  128. truthound/cli_modules/advanced/realtime.py +196 -0
  129. truthound/cli_modules/checkpoint/__init__.py +46 -0
  130. truthound/cli_modules/checkpoint/init.py +114 -0
  131. truthound/cli_modules/checkpoint/list.py +71 -0
  132. truthound/cli_modules/checkpoint/run.py +159 -0
  133. truthound/cli_modules/checkpoint/validate.py +67 -0
  134. truthound/cli_modules/common/__init__.py +71 -0
  135. truthound/cli_modules/common/errors.py +414 -0
  136. truthound/cli_modules/common/options.py +419 -0
  137. truthound/cli_modules/common/output.py +507 -0
  138. truthound/cli_modules/common/protocol.py +552 -0
  139. truthound/cli_modules/core/__init__.py +48 -0
  140. truthound/cli_modules/core/check.py +123 -0
  141. truthound/cli_modules/core/compare.py +104 -0
  142. truthound/cli_modules/core/learn.py +57 -0
  143. truthound/cli_modules/core/mask.py +77 -0
  144. truthound/cli_modules/core/profile.py +65 -0
  145. truthound/cli_modules/core/scan.py +61 -0
  146. truthound/cli_modules/profiler/__init__.py +51 -0
  147. truthound/cli_modules/profiler/auto_profile.py +175 -0
  148. truthound/cli_modules/profiler/metadata.py +107 -0
  149. truthound/cli_modules/profiler/suite.py +283 -0
  150. truthound/cli_modules/registry.py +431 -0
  151. truthound/cli_modules/scaffolding/__init__.py +89 -0
  152. truthound/cli_modules/scaffolding/base.py +631 -0
  153. truthound/cli_modules/scaffolding/commands.py +545 -0
  154. truthound/cli_modules/scaffolding/plugins.py +1072 -0
  155. truthound/cli_modules/scaffolding/reporters.py +594 -0
  156. truthound/cli_modules/scaffolding/validators.py +1127 -0
  157. truthound/common/__init__.py +18 -0
  158. truthound/common/resilience/__init__.py +130 -0
  159. truthound/common/resilience/bulkhead.py +266 -0
  160. truthound/common/resilience/circuit_breaker.py +516 -0
  161. truthound/common/resilience/composite.py +332 -0
  162. truthound/common/resilience/config.py +292 -0
  163. truthound/common/resilience/protocols.py +217 -0
  164. truthound/common/resilience/rate_limiter.py +404 -0
  165. truthound/common/resilience/retry.py +341 -0
  166. truthound/datadocs/__init__.py +260 -0
  167. truthound/datadocs/base.py +571 -0
  168. truthound/datadocs/builder.py +761 -0
  169. truthound/datadocs/charts.py +764 -0
  170. truthound/datadocs/dashboard/__init__.py +63 -0
  171. truthound/datadocs/dashboard/app.py +576 -0
  172. truthound/datadocs/dashboard/components.py +584 -0
  173. truthound/datadocs/dashboard/state.py +240 -0
  174. truthound/datadocs/engine/__init__.py +46 -0
  175. truthound/datadocs/engine/context.py +376 -0
  176. truthound/datadocs/engine/pipeline.py +618 -0
  177. truthound/datadocs/engine/registry.py +469 -0
  178. truthound/datadocs/exporters/__init__.py +49 -0
  179. truthound/datadocs/exporters/base.py +198 -0
  180. truthound/datadocs/exporters/html.py +178 -0
  181. truthound/datadocs/exporters/json_exporter.py +253 -0
  182. truthound/datadocs/exporters/markdown.py +284 -0
  183. truthound/datadocs/exporters/pdf.py +392 -0
  184. truthound/datadocs/i18n/__init__.py +86 -0
  185. truthound/datadocs/i18n/catalog.py +960 -0
  186. truthound/datadocs/i18n/formatting.py +505 -0
  187. truthound/datadocs/i18n/loader.py +256 -0
  188. truthound/datadocs/i18n/plurals.py +378 -0
  189. truthound/datadocs/renderers/__init__.py +42 -0
  190. truthound/datadocs/renderers/base.py +401 -0
  191. truthound/datadocs/renderers/custom.py +342 -0
  192. truthound/datadocs/renderers/jinja.py +697 -0
  193. truthound/datadocs/sections.py +736 -0
  194. truthound/datadocs/styles.py +931 -0
  195. truthound/datadocs/themes/__init__.py +101 -0
  196. truthound/datadocs/themes/base.py +336 -0
  197. truthound/datadocs/themes/default.py +417 -0
  198. truthound/datadocs/themes/enterprise.py +419 -0
  199. truthound/datadocs/themes/loader.py +336 -0
  200. truthound/datadocs/themes.py +301 -0
  201. truthound/datadocs/transformers/__init__.py +57 -0
  202. truthound/datadocs/transformers/base.py +268 -0
  203. truthound/datadocs/transformers/enrichers.py +544 -0
  204. truthound/datadocs/transformers/filters.py +447 -0
  205. truthound/datadocs/transformers/i18n.py +468 -0
  206. truthound/datadocs/versioning/__init__.py +62 -0
  207. truthound/datadocs/versioning/diff.py +639 -0
  208. truthound/datadocs/versioning/storage.py +497 -0
  209. truthound/datadocs/versioning/version.py +358 -0
  210. truthound/datasources/__init__.py +223 -0
  211. truthound/datasources/_async_protocols.py +222 -0
  212. truthound/datasources/_protocols.py +159 -0
  213. truthound/datasources/adapters.py +428 -0
  214. truthound/datasources/async_base.py +599 -0
  215. truthound/datasources/async_factory.py +511 -0
  216. truthound/datasources/base.py +516 -0
  217. truthound/datasources/factory.py +433 -0
  218. truthound/datasources/nosql/__init__.py +47 -0
  219. truthound/datasources/nosql/base.py +487 -0
  220. truthound/datasources/nosql/elasticsearch.py +801 -0
  221. truthound/datasources/nosql/mongodb.py +636 -0
  222. truthound/datasources/pandas_optimized.py +582 -0
  223. truthound/datasources/pandas_source.py +216 -0
  224. truthound/datasources/polars_source.py +395 -0
  225. truthound/datasources/spark_source.py +479 -0
  226. truthound/datasources/sql/__init__.py +154 -0
  227. truthound/datasources/sql/base.py +710 -0
  228. truthound/datasources/sql/bigquery.py +410 -0
  229. truthound/datasources/sql/cloud_base.py +199 -0
  230. truthound/datasources/sql/databricks.py +471 -0
  231. truthound/datasources/sql/mysql.py +316 -0
  232. truthound/datasources/sql/oracle.py +427 -0
  233. truthound/datasources/sql/postgresql.py +321 -0
  234. truthound/datasources/sql/redshift.py +479 -0
  235. truthound/datasources/sql/snowflake.py +439 -0
  236. truthound/datasources/sql/sqlite.py +286 -0
  237. truthound/datasources/sql/sqlserver.py +437 -0
  238. truthound/datasources/streaming/__init__.py +47 -0
  239. truthound/datasources/streaming/base.py +350 -0
  240. truthound/datasources/streaming/kafka.py +670 -0
  241. truthound/decorators.py +98 -0
  242. truthound/docs/__init__.py +69 -0
  243. truthound/docs/extractor.py +971 -0
  244. truthound/docs/generator.py +601 -0
  245. truthound/docs/parser.py +1037 -0
  246. truthound/docs/renderer.py +999 -0
  247. truthound/drift/__init__.py +22 -0
  248. truthound/drift/compare.py +189 -0
  249. truthound/drift/detectors.py +464 -0
  250. truthound/drift/report.py +160 -0
  251. truthound/execution/__init__.py +65 -0
  252. truthound/execution/_protocols.py +324 -0
  253. truthound/execution/base.py +576 -0
  254. truthound/execution/distributed/__init__.py +179 -0
  255. truthound/execution/distributed/aggregations.py +731 -0
  256. truthound/execution/distributed/arrow_bridge.py +817 -0
  257. truthound/execution/distributed/base.py +550 -0
  258. truthound/execution/distributed/dask_engine.py +976 -0
  259. truthound/execution/distributed/mixins.py +766 -0
  260. truthound/execution/distributed/protocols.py +756 -0
  261. truthound/execution/distributed/ray_engine.py +1127 -0
  262. truthound/execution/distributed/registry.py +446 -0
  263. truthound/execution/distributed/spark_engine.py +1011 -0
  264. truthound/execution/distributed/validator_adapter.py +682 -0
  265. truthound/execution/pandas_engine.py +401 -0
  266. truthound/execution/polars_engine.py +497 -0
  267. truthound/execution/pushdown/__init__.py +230 -0
  268. truthound/execution/pushdown/ast.py +1550 -0
  269. truthound/execution/pushdown/builder.py +1550 -0
  270. truthound/execution/pushdown/dialects.py +1072 -0
  271. truthound/execution/pushdown/executor.py +829 -0
  272. truthound/execution/pushdown/optimizer.py +1041 -0
  273. truthound/execution/sql_engine.py +518 -0
  274. truthound/infrastructure/__init__.py +189 -0
  275. truthound/infrastructure/audit.py +1515 -0
  276. truthound/infrastructure/config.py +1133 -0
  277. truthound/infrastructure/encryption.py +1132 -0
  278. truthound/infrastructure/logging.py +1503 -0
  279. truthound/infrastructure/metrics.py +1220 -0
  280. truthound/lineage/__init__.py +89 -0
  281. truthound/lineage/base.py +746 -0
  282. truthound/lineage/impact_analysis.py +474 -0
  283. truthound/lineage/integrations/__init__.py +22 -0
  284. truthound/lineage/integrations/openlineage.py +548 -0
  285. truthound/lineage/tracker.py +512 -0
  286. truthound/lineage/visualization/__init__.py +33 -0
  287. truthound/lineage/visualization/protocols.py +145 -0
  288. truthound/lineage/visualization/renderers/__init__.py +20 -0
  289. truthound/lineage/visualization/renderers/cytoscape.py +329 -0
  290. truthound/lineage/visualization/renderers/d3.py +331 -0
  291. truthound/lineage/visualization/renderers/graphviz.py +276 -0
  292. truthound/lineage/visualization/renderers/mermaid.py +308 -0
  293. truthound/maskers.py +113 -0
  294. truthound/ml/__init__.py +124 -0
  295. truthound/ml/anomaly_models/__init__.py +31 -0
  296. truthound/ml/anomaly_models/ensemble.py +362 -0
  297. truthound/ml/anomaly_models/isolation_forest.py +444 -0
  298. truthound/ml/anomaly_models/statistical.py +392 -0
  299. truthound/ml/base.py +1178 -0
  300. truthound/ml/drift_detection/__init__.py +26 -0
  301. truthound/ml/drift_detection/concept.py +381 -0
  302. truthound/ml/drift_detection/distribution.py +361 -0
  303. truthound/ml/drift_detection/feature.py +442 -0
  304. truthound/ml/drift_detection/multivariate.py +495 -0
  305. truthound/ml/monitoring/__init__.py +88 -0
  306. truthound/ml/monitoring/alerting/__init__.py +33 -0
  307. truthound/ml/monitoring/alerting/handlers.py +427 -0
  308. truthound/ml/monitoring/alerting/rules.py +508 -0
  309. truthound/ml/monitoring/collectors/__init__.py +19 -0
  310. truthound/ml/monitoring/collectors/composite.py +105 -0
  311. truthound/ml/monitoring/collectors/drift.py +324 -0
  312. truthound/ml/monitoring/collectors/performance.py +179 -0
  313. truthound/ml/monitoring/collectors/quality.py +369 -0
  314. truthound/ml/monitoring/monitor.py +536 -0
  315. truthound/ml/monitoring/protocols.py +451 -0
  316. truthound/ml/monitoring/stores/__init__.py +15 -0
  317. truthound/ml/monitoring/stores/memory.py +201 -0
  318. truthound/ml/monitoring/stores/prometheus.py +296 -0
  319. truthound/ml/rule_learning/__init__.py +25 -0
  320. truthound/ml/rule_learning/constraint_miner.py +443 -0
  321. truthound/ml/rule_learning/pattern_learner.py +499 -0
  322. truthound/ml/rule_learning/profile_learner.py +462 -0
  323. truthound/multitenancy/__init__.py +326 -0
  324. truthound/multitenancy/core.py +852 -0
  325. truthound/multitenancy/integration.py +597 -0
  326. truthound/multitenancy/isolation.py +630 -0
  327. truthound/multitenancy/manager.py +770 -0
  328. truthound/multitenancy/middleware.py +765 -0
  329. truthound/multitenancy/quota.py +537 -0
  330. truthound/multitenancy/resolvers.py +603 -0
  331. truthound/multitenancy/storage.py +703 -0
  332. truthound/observability/__init__.py +307 -0
  333. truthound/observability/context.py +531 -0
  334. truthound/observability/instrumentation.py +611 -0
  335. truthound/observability/logging.py +887 -0
  336. truthound/observability/metrics.py +1157 -0
  337. truthound/observability/tracing/__init__.py +178 -0
  338. truthound/observability/tracing/baggage.py +310 -0
  339. truthound/observability/tracing/config.py +426 -0
  340. truthound/observability/tracing/exporter.py +787 -0
  341. truthound/observability/tracing/integration.py +1018 -0
  342. truthound/observability/tracing/otel/__init__.py +146 -0
  343. truthound/observability/tracing/otel/adapter.py +982 -0
  344. truthound/observability/tracing/otel/bridge.py +1177 -0
  345. truthound/observability/tracing/otel/compat.py +681 -0
  346. truthound/observability/tracing/otel/config.py +691 -0
  347. truthound/observability/tracing/otel/detection.py +327 -0
  348. truthound/observability/tracing/otel/protocols.py +426 -0
  349. truthound/observability/tracing/processor.py +561 -0
  350. truthound/observability/tracing/propagator.py +757 -0
  351. truthound/observability/tracing/provider.py +569 -0
  352. truthound/observability/tracing/resource.py +515 -0
  353. truthound/observability/tracing/sampler.py +487 -0
  354. truthound/observability/tracing/span.py +676 -0
  355. truthound/plugins/__init__.py +198 -0
  356. truthound/plugins/base.py +599 -0
  357. truthound/plugins/cli.py +680 -0
  358. truthound/plugins/dependencies/__init__.py +42 -0
  359. truthound/plugins/dependencies/graph.py +422 -0
  360. truthound/plugins/dependencies/resolver.py +417 -0
  361. truthound/plugins/discovery.py +379 -0
  362. truthound/plugins/docs/__init__.py +46 -0
  363. truthound/plugins/docs/extractor.py +444 -0
  364. truthound/plugins/docs/renderer.py +499 -0
  365. truthound/plugins/enterprise_manager.py +877 -0
  366. truthound/plugins/examples/__init__.py +19 -0
  367. truthound/plugins/examples/custom_validators.py +317 -0
  368. truthound/plugins/examples/slack_notifier.py +312 -0
  369. truthound/plugins/examples/xml_reporter.py +254 -0
  370. truthound/plugins/hooks.py +558 -0
  371. truthound/plugins/lifecycle/__init__.py +43 -0
  372. truthound/plugins/lifecycle/hot_reload.py +402 -0
  373. truthound/plugins/lifecycle/manager.py +371 -0
  374. truthound/plugins/manager.py +736 -0
  375. truthound/plugins/registry.py +338 -0
  376. truthound/plugins/security/__init__.py +93 -0
  377. truthound/plugins/security/exceptions.py +332 -0
  378. truthound/plugins/security/policies.py +348 -0
  379. truthound/plugins/security/protocols.py +643 -0
  380. truthound/plugins/security/sandbox/__init__.py +45 -0
  381. truthound/plugins/security/sandbox/context.py +158 -0
  382. truthound/plugins/security/sandbox/engines/__init__.py +19 -0
  383. truthound/plugins/security/sandbox/engines/container.py +379 -0
  384. truthound/plugins/security/sandbox/engines/noop.py +144 -0
  385. truthound/plugins/security/sandbox/engines/process.py +336 -0
  386. truthound/plugins/security/sandbox/factory.py +211 -0
  387. truthound/plugins/security/signing/__init__.py +57 -0
  388. truthound/plugins/security/signing/service.py +330 -0
  389. truthound/plugins/security/signing/trust_store.py +368 -0
  390. truthound/plugins/security/signing/verifier.py +459 -0
  391. truthound/plugins/versioning/__init__.py +41 -0
  392. truthound/plugins/versioning/constraints.py +297 -0
  393. truthound/plugins/versioning/resolver.py +329 -0
  394. truthound/profiler/__init__.py +1729 -0
  395. truthound/profiler/_lazy.py +452 -0
  396. truthound/profiler/ab_testing/__init__.py +80 -0
  397. truthound/profiler/ab_testing/analysis.py +449 -0
  398. truthound/profiler/ab_testing/base.py +257 -0
  399. truthound/profiler/ab_testing/experiment.py +395 -0
  400. truthound/profiler/ab_testing/tracking.py +368 -0
  401. truthound/profiler/auto_threshold.py +1170 -0
  402. truthound/profiler/base.py +579 -0
  403. truthound/profiler/cache_patterns.py +911 -0
  404. truthound/profiler/caching.py +1303 -0
  405. truthound/profiler/column_profiler.py +712 -0
  406. truthound/profiler/comparison.py +1007 -0
  407. truthound/profiler/custom_patterns.py +1170 -0
  408. truthound/profiler/dashboard/__init__.py +50 -0
  409. truthound/profiler/dashboard/app.py +476 -0
  410. truthound/profiler/dashboard/components.py +457 -0
  411. truthound/profiler/dashboard/config.py +72 -0
  412. truthound/profiler/distributed/__init__.py +83 -0
  413. truthound/profiler/distributed/base.py +281 -0
  414. truthound/profiler/distributed/dask_backend.py +498 -0
  415. truthound/profiler/distributed/local_backend.py +293 -0
  416. truthound/profiler/distributed/profiler.py +304 -0
  417. truthound/profiler/distributed/ray_backend.py +374 -0
  418. truthound/profiler/distributed/spark_backend.py +375 -0
  419. truthound/profiler/distributed.py +1366 -0
  420. truthound/profiler/enterprise_sampling.py +1065 -0
  421. truthound/profiler/errors.py +488 -0
  422. truthound/profiler/evolution/__init__.py +91 -0
  423. truthound/profiler/evolution/alerts.py +426 -0
  424. truthound/profiler/evolution/changes.py +206 -0
  425. truthound/profiler/evolution/compatibility.py +365 -0
  426. truthound/profiler/evolution/detector.py +372 -0
  427. truthound/profiler/evolution/protocols.py +121 -0
  428. truthound/profiler/generators/__init__.py +48 -0
  429. truthound/profiler/generators/base.py +384 -0
  430. truthound/profiler/generators/ml_rules.py +375 -0
  431. truthound/profiler/generators/pattern_rules.py +384 -0
  432. truthound/profiler/generators/schema_rules.py +267 -0
  433. truthound/profiler/generators/stats_rules.py +324 -0
  434. truthound/profiler/generators/suite_generator.py +857 -0
  435. truthound/profiler/i18n.py +1542 -0
  436. truthound/profiler/incremental.py +554 -0
  437. truthound/profiler/incremental_validation.py +1710 -0
  438. truthound/profiler/integration/__init__.py +73 -0
  439. truthound/profiler/integration/adapters.py +345 -0
  440. truthound/profiler/integration/context.py +371 -0
  441. truthound/profiler/integration/executor.py +527 -0
  442. truthound/profiler/integration/naming.py +75 -0
  443. truthound/profiler/integration/protocols.py +243 -0
  444. truthound/profiler/memory.py +1185 -0
  445. truthound/profiler/migration/__init__.py +60 -0
  446. truthound/profiler/migration/base.py +345 -0
  447. truthound/profiler/migration/manager.py +444 -0
  448. truthound/profiler/migration/v1_0_to_v1_1.py +484 -0
  449. truthound/profiler/ml/__init__.py +73 -0
  450. truthound/profiler/ml/base.py +244 -0
  451. truthound/profiler/ml/classifier.py +507 -0
  452. truthound/profiler/ml/feature_extraction.py +604 -0
  453. truthound/profiler/ml/pretrained.py +448 -0
  454. truthound/profiler/ml_inference.py +1276 -0
  455. truthound/profiler/native_patterns.py +815 -0
  456. truthound/profiler/observability.py +1184 -0
  457. truthound/profiler/process_timeout.py +1566 -0
  458. truthound/profiler/progress.py +568 -0
  459. truthound/profiler/progress_callbacks.py +1734 -0
  460. truthound/profiler/quality.py +1345 -0
  461. truthound/profiler/resilience.py +1180 -0
  462. truthound/profiler/sampled_matcher.py +794 -0
  463. truthound/profiler/sampling.py +1288 -0
  464. truthound/profiler/scheduling/__init__.py +82 -0
  465. truthound/profiler/scheduling/protocols.py +214 -0
  466. truthound/profiler/scheduling/scheduler.py +474 -0
  467. truthound/profiler/scheduling/storage.py +457 -0
  468. truthound/profiler/scheduling/triggers.py +449 -0
  469. truthound/profiler/schema.py +603 -0
  470. truthound/profiler/streaming.py +685 -0
  471. truthound/profiler/streaming_patterns.py +1354 -0
  472. truthound/profiler/suite_cli.py +625 -0
  473. truthound/profiler/suite_config.py +789 -0
  474. truthound/profiler/suite_export.py +1268 -0
  475. truthound/profiler/table_profiler.py +547 -0
  476. truthound/profiler/timeout.py +565 -0
  477. truthound/profiler/validation.py +1532 -0
  478. truthound/profiler/visualization/__init__.py +118 -0
  479. truthound/profiler/visualization/base.py +346 -0
  480. truthound/profiler/visualization/generator.py +1259 -0
  481. truthound/profiler/visualization/plotly_renderer.py +811 -0
  482. truthound/profiler/visualization/renderers.py +669 -0
  483. truthound/profiler/visualization/sections.py +540 -0
  484. truthound/profiler/visualization.py +2122 -0
  485. truthound/profiler/yaml_validation.py +1151 -0
  486. truthound/py.typed +0 -0
  487. truthound/ratelimit/__init__.py +248 -0
  488. truthound/ratelimit/algorithms.py +1108 -0
  489. truthound/ratelimit/core.py +573 -0
  490. truthound/ratelimit/integration.py +532 -0
  491. truthound/ratelimit/limiter.py +663 -0
  492. truthound/ratelimit/middleware.py +700 -0
  493. truthound/ratelimit/policy.py +792 -0
  494. truthound/ratelimit/storage.py +763 -0
  495. truthound/rbac/__init__.py +340 -0
  496. truthound/rbac/core.py +976 -0
  497. truthound/rbac/integration.py +760 -0
  498. truthound/rbac/manager.py +1052 -0
  499. truthound/rbac/middleware.py +842 -0
  500. truthound/rbac/policy.py +954 -0
  501. truthound/rbac/storage.py +878 -0
  502. truthound/realtime/__init__.py +141 -0
  503. truthound/realtime/adapters/__init__.py +43 -0
  504. truthound/realtime/adapters/base.py +533 -0
  505. truthound/realtime/adapters/kafka.py +487 -0
  506. truthound/realtime/adapters/kinesis.py +479 -0
  507. truthound/realtime/adapters/mock.py +243 -0
  508. truthound/realtime/base.py +553 -0
  509. truthound/realtime/factory.py +382 -0
  510. truthound/realtime/incremental.py +660 -0
  511. truthound/realtime/processing/__init__.py +67 -0
  512. truthound/realtime/processing/exactly_once.py +575 -0
  513. truthound/realtime/processing/state.py +547 -0
  514. truthound/realtime/processing/windows.py +647 -0
  515. truthound/realtime/protocols.py +569 -0
  516. truthound/realtime/streaming.py +605 -0
  517. truthound/realtime/testing/__init__.py +32 -0
  518. truthound/realtime/testing/containers.py +615 -0
  519. truthound/realtime/testing/fixtures.py +484 -0
  520. truthound/report.py +280 -0
  521. truthound/reporters/__init__.py +46 -0
  522. truthound/reporters/_protocols.py +30 -0
  523. truthound/reporters/base.py +324 -0
  524. truthound/reporters/ci/__init__.py +66 -0
  525. truthound/reporters/ci/azure.py +436 -0
  526. truthound/reporters/ci/base.py +509 -0
  527. truthound/reporters/ci/bitbucket.py +567 -0
  528. truthound/reporters/ci/circleci.py +547 -0
  529. truthound/reporters/ci/detection.py +364 -0
  530. truthound/reporters/ci/factory.py +182 -0
  531. truthound/reporters/ci/github.py +388 -0
  532. truthound/reporters/ci/gitlab.py +471 -0
  533. truthound/reporters/ci/jenkins.py +525 -0
  534. truthound/reporters/console_reporter.py +299 -0
  535. truthound/reporters/factory.py +211 -0
  536. truthound/reporters/html_reporter.py +524 -0
  537. truthound/reporters/json_reporter.py +256 -0
  538. truthound/reporters/markdown_reporter.py +280 -0
  539. truthound/reporters/sdk/__init__.py +174 -0
  540. truthound/reporters/sdk/builder.py +558 -0
  541. truthound/reporters/sdk/mixins.py +1150 -0
  542. truthound/reporters/sdk/schema.py +1493 -0
  543. truthound/reporters/sdk/templates.py +666 -0
  544. truthound/reporters/sdk/testing.py +968 -0
  545. truthound/scanners.py +170 -0
  546. truthound/scheduling/__init__.py +122 -0
  547. truthound/scheduling/cron.py +1136 -0
  548. truthound/scheduling/presets.py +212 -0
  549. truthound/schema.py +275 -0
  550. truthound/secrets/__init__.py +173 -0
  551. truthound/secrets/base.py +618 -0
  552. truthound/secrets/cloud.py +682 -0
  553. truthound/secrets/integration.py +507 -0
  554. truthound/secrets/manager.py +633 -0
  555. truthound/secrets/oidc/__init__.py +172 -0
  556. truthound/secrets/oidc/base.py +902 -0
  557. truthound/secrets/oidc/credential_provider.py +623 -0
  558. truthound/secrets/oidc/exchangers.py +1001 -0
  559. truthound/secrets/oidc/github/__init__.py +110 -0
  560. truthound/secrets/oidc/github/claims.py +718 -0
  561. truthound/secrets/oidc/github/enhanced_provider.py +693 -0
  562. truthound/secrets/oidc/github/trust_policy.py +742 -0
  563. truthound/secrets/oidc/github/verification.py +723 -0
  564. truthound/secrets/oidc/github/workflow.py +691 -0
  565. truthound/secrets/oidc/providers.py +825 -0
  566. truthound/secrets/providers.py +506 -0
  567. truthound/secrets/resolver.py +495 -0
  568. truthound/stores/__init__.py +177 -0
  569. truthound/stores/backends/__init__.py +18 -0
  570. truthound/stores/backends/_protocols.py +340 -0
  571. truthound/stores/backends/azure_blob.py +530 -0
  572. truthound/stores/backends/concurrent_filesystem.py +915 -0
  573. truthound/stores/backends/connection_pool.py +1365 -0
  574. truthound/stores/backends/database.py +743 -0
  575. truthound/stores/backends/filesystem.py +538 -0
  576. truthound/stores/backends/gcs.py +399 -0
  577. truthound/stores/backends/memory.py +354 -0
  578. truthound/stores/backends/s3.py +434 -0
  579. truthound/stores/backpressure/__init__.py +84 -0
  580. truthound/stores/backpressure/base.py +375 -0
  581. truthound/stores/backpressure/circuit_breaker.py +434 -0
  582. truthound/stores/backpressure/monitor.py +376 -0
  583. truthound/stores/backpressure/strategies.py +677 -0
  584. truthound/stores/base.py +551 -0
  585. truthound/stores/batching/__init__.py +65 -0
  586. truthound/stores/batching/base.py +305 -0
  587. truthound/stores/batching/buffer.py +370 -0
  588. truthound/stores/batching/store.py +248 -0
  589. truthound/stores/batching/writer.py +521 -0
  590. truthound/stores/caching/__init__.py +60 -0
  591. truthound/stores/caching/backends.py +684 -0
  592. truthound/stores/caching/base.py +356 -0
  593. truthound/stores/caching/store.py +305 -0
  594. truthound/stores/compression/__init__.py +193 -0
  595. truthound/stores/compression/adaptive.py +694 -0
  596. truthound/stores/compression/base.py +514 -0
  597. truthound/stores/compression/pipeline.py +868 -0
  598. truthound/stores/compression/providers.py +672 -0
  599. truthound/stores/compression/streaming.py +832 -0
  600. truthound/stores/concurrency/__init__.py +81 -0
  601. truthound/stores/concurrency/atomic.py +556 -0
  602. truthound/stores/concurrency/index.py +775 -0
  603. truthound/stores/concurrency/locks.py +576 -0
  604. truthound/stores/concurrency/manager.py +482 -0
  605. truthound/stores/encryption/__init__.py +297 -0
  606. truthound/stores/encryption/base.py +952 -0
  607. truthound/stores/encryption/keys.py +1191 -0
  608. truthound/stores/encryption/pipeline.py +903 -0
  609. truthound/stores/encryption/providers.py +953 -0
  610. truthound/stores/encryption/streaming.py +950 -0
  611. truthound/stores/expectations.py +227 -0
  612. truthound/stores/factory.py +246 -0
  613. truthound/stores/migration/__init__.py +75 -0
  614. truthound/stores/migration/base.py +480 -0
  615. truthound/stores/migration/manager.py +347 -0
  616. truthound/stores/migration/registry.py +382 -0
  617. truthound/stores/migration/store.py +559 -0
  618. truthound/stores/observability/__init__.py +106 -0
  619. truthound/stores/observability/audit.py +718 -0
  620. truthound/stores/observability/config.py +270 -0
  621. truthound/stores/observability/factory.py +208 -0
  622. truthound/stores/observability/metrics.py +636 -0
  623. truthound/stores/observability/protocols.py +410 -0
  624. truthound/stores/observability/store.py +570 -0
  625. truthound/stores/observability/tracing.py +784 -0
  626. truthound/stores/replication/__init__.py +76 -0
  627. truthound/stores/replication/base.py +260 -0
  628. truthound/stores/replication/monitor.py +269 -0
  629. truthound/stores/replication/store.py +439 -0
  630. truthound/stores/replication/syncer.py +391 -0
  631. truthound/stores/results.py +359 -0
  632. truthound/stores/retention/__init__.py +77 -0
  633. truthound/stores/retention/base.py +378 -0
  634. truthound/stores/retention/policies.py +621 -0
  635. truthound/stores/retention/scheduler.py +279 -0
  636. truthound/stores/retention/store.py +526 -0
  637. truthound/stores/streaming/__init__.py +138 -0
  638. truthound/stores/streaming/base.py +801 -0
  639. truthound/stores/streaming/database.py +984 -0
  640. truthound/stores/streaming/filesystem.py +719 -0
  641. truthound/stores/streaming/reader.py +629 -0
  642. truthound/stores/streaming/s3.py +843 -0
  643. truthound/stores/streaming/writer.py +790 -0
  644. truthound/stores/tiering/__init__.py +108 -0
  645. truthound/stores/tiering/base.py +462 -0
  646. truthound/stores/tiering/manager.py +249 -0
  647. truthound/stores/tiering/policies.py +692 -0
  648. truthound/stores/tiering/store.py +526 -0
  649. truthound/stores/versioning/__init__.py +56 -0
  650. truthound/stores/versioning/base.py +376 -0
  651. truthound/stores/versioning/store.py +660 -0
  652. truthound/stores/versioning/strategies.py +353 -0
  653. truthound/types.py +56 -0
  654. truthound/validators/__init__.py +774 -0
  655. truthound/validators/aggregate/__init__.py +27 -0
  656. truthound/validators/aggregate/central.py +116 -0
  657. truthound/validators/aggregate/extremes.py +116 -0
  658. truthound/validators/aggregate/spread.py +118 -0
  659. truthound/validators/aggregate/sum.py +64 -0
  660. truthound/validators/aggregate/type.py +78 -0
  661. truthound/validators/anomaly/__init__.py +93 -0
  662. truthound/validators/anomaly/base.py +431 -0
  663. truthound/validators/anomaly/ml_based.py +1190 -0
  664. truthound/validators/anomaly/multivariate.py +647 -0
  665. truthound/validators/anomaly/statistical.py +599 -0
  666. truthound/validators/base.py +1089 -0
  667. truthound/validators/business_rule/__init__.py +46 -0
  668. truthound/validators/business_rule/base.py +147 -0
  669. truthound/validators/business_rule/checksum.py +509 -0
  670. truthound/validators/business_rule/financial.py +526 -0
  671. truthound/validators/cache.py +733 -0
  672. truthound/validators/completeness/__init__.py +39 -0
  673. truthound/validators/completeness/conditional.py +73 -0
  674. truthound/validators/completeness/default.py +98 -0
  675. truthound/validators/completeness/empty.py +103 -0
  676. truthound/validators/completeness/nan.py +337 -0
  677. truthound/validators/completeness/null.py +152 -0
  678. truthound/validators/cross_table/__init__.py +17 -0
  679. truthound/validators/cross_table/aggregate.py +333 -0
  680. truthound/validators/cross_table/row_count.py +122 -0
  681. truthound/validators/datetime/__init__.py +29 -0
  682. truthound/validators/datetime/format.py +78 -0
  683. truthound/validators/datetime/freshness.py +269 -0
  684. truthound/validators/datetime/order.py +73 -0
  685. truthound/validators/datetime/parseable.py +185 -0
  686. truthound/validators/datetime/range.py +202 -0
  687. truthound/validators/datetime/timezone.py +69 -0
  688. truthound/validators/distribution/__init__.py +49 -0
  689. truthound/validators/distribution/distribution.py +128 -0
  690. truthound/validators/distribution/monotonic.py +119 -0
  691. truthound/validators/distribution/outlier.py +178 -0
  692. truthound/validators/distribution/quantile.py +80 -0
  693. truthound/validators/distribution/range.py +254 -0
  694. truthound/validators/distribution/set.py +125 -0
  695. truthound/validators/distribution/statistical.py +459 -0
  696. truthound/validators/drift/__init__.py +79 -0
  697. truthound/validators/drift/base.py +427 -0
  698. truthound/validators/drift/multi_feature.py +401 -0
  699. truthound/validators/drift/numeric.py +395 -0
  700. truthound/validators/drift/psi.py +446 -0
  701. truthound/validators/drift/statistical.py +510 -0
  702. truthound/validators/enterprise.py +1658 -0
  703. truthound/validators/geospatial/__init__.py +80 -0
  704. truthound/validators/geospatial/base.py +97 -0
  705. truthound/validators/geospatial/boundary.py +238 -0
  706. truthound/validators/geospatial/coordinate.py +351 -0
  707. truthound/validators/geospatial/distance.py +399 -0
  708. truthound/validators/geospatial/polygon.py +665 -0
  709. truthound/validators/i18n/__init__.py +308 -0
  710. truthound/validators/i18n/bidi.py +571 -0
  711. truthound/validators/i18n/catalogs.py +570 -0
  712. truthound/validators/i18n/dialects.py +763 -0
  713. truthound/validators/i18n/extended_catalogs.py +549 -0
  714. truthound/validators/i18n/formatting.py +1434 -0
  715. truthound/validators/i18n/loader.py +1020 -0
  716. truthound/validators/i18n/messages.py +521 -0
  717. truthound/validators/i18n/plural.py +683 -0
  718. truthound/validators/i18n/protocols.py +855 -0
  719. truthound/validators/i18n/tms.py +1162 -0
  720. truthound/validators/localization/__init__.py +53 -0
  721. truthound/validators/localization/base.py +122 -0
  722. truthound/validators/localization/chinese.py +362 -0
  723. truthound/validators/localization/japanese.py +275 -0
  724. truthound/validators/localization/korean.py +524 -0
  725. truthound/validators/memory/__init__.py +94 -0
  726. truthound/validators/memory/approximate_knn.py +506 -0
  727. truthound/validators/memory/base.py +547 -0
  728. truthound/validators/memory/sgd_online.py +719 -0
  729. truthound/validators/memory/streaming_ecdf.py +753 -0
  730. truthound/validators/ml_feature/__init__.py +54 -0
  731. truthound/validators/ml_feature/base.py +249 -0
  732. truthound/validators/ml_feature/correlation.py +299 -0
  733. truthound/validators/ml_feature/leakage.py +344 -0
  734. truthound/validators/ml_feature/null_impact.py +270 -0
  735. truthound/validators/ml_feature/scale.py +264 -0
  736. truthound/validators/multi_column/__init__.py +89 -0
  737. truthound/validators/multi_column/arithmetic.py +284 -0
  738. truthound/validators/multi_column/base.py +231 -0
  739. truthound/validators/multi_column/comparison.py +273 -0
  740. truthound/validators/multi_column/consistency.py +312 -0
  741. truthound/validators/multi_column/statistical.py +299 -0
  742. truthound/validators/optimization/__init__.py +164 -0
  743. truthound/validators/optimization/aggregation.py +563 -0
  744. truthound/validators/optimization/covariance.py +556 -0
  745. truthound/validators/optimization/geo.py +626 -0
  746. truthound/validators/optimization/graph.py +587 -0
  747. truthound/validators/optimization/orchestrator.py +970 -0
  748. truthound/validators/optimization/profiling.py +1312 -0
  749. truthound/validators/privacy/__init__.py +223 -0
  750. truthound/validators/privacy/base.py +635 -0
  751. truthound/validators/privacy/ccpa.py +670 -0
  752. truthound/validators/privacy/gdpr.py +728 -0
  753. truthound/validators/privacy/global_patterns.py +604 -0
  754. truthound/validators/privacy/plugins.py +867 -0
  755. truthound/validators/profiling/__init__.py +52 -0
  756. truthound/validators/profiling/base.py +175 -0
  757. truthound/validators/profiling/cardinality.py +312 -0
  758. truthound/validators/profiling/entropy.py +391 -0
  759. truthound/validators/profiling/frequency.py +455 -0
  760. truthound/validators/pushdown_support.py +660 -0
  761. truthound/validators/query/__init__.py +91 -0
  762. truthound/validators/query/aggregate.py +346 -0
  763. truthound/validators/query/base.py +246 -0
  764. truthound/validators/query/column.py +249 -0
  765. truthound/validators/query/expression.py +274 -0
  766. truthound/validators/query/result.py +323 -0
  767. truthound/validators/query/row_count.py +264 -0
  768. truthound/validators/referential/__init__.py +80 -0
  769. truthound/validators/referential/base.py +395 -0
  770. truthound/validators/referential/cascade.py +391 -0
  771. truthound/validators/referential/circular.py +563 -0
  772. truthound/validators/referential/foreign_key.py +624 -0
  773. truthound/validators/referential/orphan.py +485 -0
  774. truthound/validators/registry.py +112 -0
  775. truthound/validators/schema/__init__.py +41 -0
  776. truthound/validators/schema/column_count.py +142 -0
  777. truthound/validators/schema/column_exists.py +80 -0
  778. truthound/validators/schema/column_order.py +82 -0
  779. truthound/validators/schema/column_pair.py +85 -0
  780. truthound/validators/schema/column_pair_set.py +195 -0
  781. truthound/validators/schema/column_type.py +94 -0
  782. truthound/validators/schema/multi_column.py +53 -0
  783. truthound/validators/schema/multi_column_aggregate.py +175 -0
  784. truthound/validators/schema/referential.py +274 -0
  785. truthound/validators/schema/table_schema.py +91 -0
  786. truthound/validators/schema_validator.py +219 -0
  787. truthound/validators/sdk/__init__.py +250 -0
  788. truthound/validators/sdk/builder.py +680 -0
  789. truthound/validators/sdk/decorators.py +474 -0
  790. truthound/validators/sdk/enterprise/__init__.py +211 -0
  791. truthound/validators/sdk/enterprise/docs.py +725 -0
  792. truthound/validators/sdk/enterprise/fuzzing.py +659 -0
  793. truthound/validators/sdk/enterprise/licensing.py +709 -0
  794. truthound/validators/sdk/enterprise/manager.py +543 -0
  795. truthound/validators/sdk/enterprise/resources.py +628 -0
  796. truthound/validators/sdk/enterprise/sandbox.py +766 -0
  797. truthound/validators/sdk/enterprise/signing.py +603 -0
  798. truthound/validators/sdk/enterprise/templates.py +865 -0
  799. truthound/validators/sdk/enterprise/versioning.py +659 -0
  800. truthound/validators/sdk/templates.py +757 -0
  801. truthound/validators/sdk/testing.py +807 -0
  802. truthound/validators/security/__init__.py +181 -0
  803. truthound/validators/security/redos/__init__.py +182 -0
  804. truthound/validators/security/redos/core.py +861 -0
  805. truthound/validators/security/redos/cpu_monitor.py +593 -0
  806. truthound/validators/security/redos/cve_database.py +791 -0
  807. truthound/validators/security/redos/ml/__init__.py +155 -0
  808. truthound/validators/security/redos/ml/base.py +785 -0
  809. truthound/validators/security/redos/ml/datasets.py +618 -0
  810. truthound/validators/security/redos/ml/features.py +359 -0
  811. truthound/validators/security/redos/ml/models.py +1000 -0
  812. truthound/validators/security/redos/ml/predictor.py +507 -0
  813. truthound/validators/security/redos/ml/storage.py +632 -0
  814. truthound/validators/security/redos/ml/training.py +571 -0
  815. truthound/validators/security/redos/ml_analyzer.py +937 -0
  816. truthound/validators/security/redos/optimizer.py +674 -0
  817. truthound/validators/security/redos/profiler.py +682 -0
  818. truthound/validators/security/redos/re2_engine.py +709 -0
  819. truthound/validators/security/redos.py +886 -0
  820. truthound/validators/security/sql_security.py +1247 -0
  821. truthound/validators/streaming/__init__.py +126 -0
  822. truthound/validators/streaming/base.py +292 -0
  823. truthound/validators/streaming/completeness.py +210 -0
  824. truthound/validators/streaming/mixin.py +575 -0
  825. truthound/validators/streaming/range.py +308 -0
  826. truthound/validators/streaming/sources.py +846 -0
  827. truthound/validators/string/__init__.py +57 -0
  828. truthound/validators/string/casing.py +158 -0
  829. truthound/validators/string/charset.py +96 -0
  830. truthound/validators/string/format.py +501 -0
  831. truthound/validators/string/json.py +77 -0
  832. truthound/validators/string/json_schema.py +184 -0
  833. truthound/validators/string/length.py +104 -0
  834. truthound/validators/string/like_pattern.py +237 -0
  835. truthound/validators/string/regex.py +202 -0
  836. truthound/validators/string/regex_extended.py +435 -0
  837. truthound/validators/table/__init__.py +88 -0
  838. truthound/validators/table/base.py +78 -0
  839. truthound/validators/table/column_count.py +198 -0
  840. truthound/validators/table/freshness.py +362 -0
  841. truthound/validators/table/row_count.py +251 -0
  842. truthound/validators/table/schema.py +333 -0
  843. truthound/validators/table/size.py +285 -0
  844. truthound/validators/timeout/__init__.py +102 -0
  845. truthound/validators/timeout/advanced/__init__.py +247 -0
  846. truthound/validators/timeout/advanced/circuit_breaker.py +675 -0
  847. truthound/validators/timeout/advanced/prediction.py +773 -0
  848. truthound/validators/timeout/advanced/priority.py +618 -0
  849. truthound/validators/timeout/advanced/redis_backend.py +770 -0
  850. truthound/validators/timeout/advanced/retry.py +721 -0
  851. truthound/validators/timeout/advanced/sampling.py +788 -0
  852. truthound/validators/timeout/advanced/sla.py +661 -0
  853. truthound/validators/timeout/advanced/telemetry.py +804 -0
  854. truthound/validators/timeout/cascade.py +477 -0
  855. truthound/validators/timeout/deadline.py +657 -0
  856. truthound/validators/timeout/degradation.py +525 -0
  857. truthound/validators/timeout/distributed.py +597 -0
  858. truthound/validators/timeseries/__init__.py +89 -0
  859. truthound/validators/timeseries/base.py +326 -0
  860. truthound/validators/timeseries/completeness.py +617 -0
  861. truthound/validators/timeseries/gap.py +485 -0
  862. truthound/validators/timeseries/monotonic.py +310 -0
  863. truthound/validators/timeseries/seasonality.py +422 -0
  864. truthound/validators/timeseries/trend.py +510 -0
  865. truthound/validators/uniqueness/__init__.py +59 -0
  866. truthound/validators/uniqueness/approximate.py +475 -0
  867. truthound/validators/uniqueness/distinct_values.py +253 -0
  868. truthound/validators/uniqueness/duplicate.py +118 -0
  869. truthound/validators/uniqueness/primary_key.py +140 -0
  870. truthound/validators/uniqueness/unique.py +191 -0
  871. truthound/validators/uniqueness/within_record.py +599 -0
  872. truthound/validators/utils.py +756 -0
  873. truthound-1.0.8.dist-info/METADATA +474 -0
  874. truthound-1.0.8.dist-info/RECORD +877 -0
  875. truthound-1.0.8.dist-info/WHEEL +4 -0
  876. truthound-1.0.8.dist-info/entry_points.txt +2 -0
  877. truthound-1.0.8.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,878 @@
1
+ """Storage backends for RBAC.
2
+
3
+ This module provides various storage backends for persisting roles,
4
+ principals, and permissions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import threading
11
+ import time
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from truthound.rbac.core import (
18
+ Permission,
19
+ Principal,
20
+ PrincipalStore,
21
+ PrincipalType,
22
+ Role,
23
+ RoleStore,
24
+ RoleType,
25
+ CircularRoleError,
26
+ )
27
+
28
+
29
+ # =============================================================================
30
+ # In-Memory Storage
31
+ # =============================================================================
32
+
33
+
34
+ class MemoryRoleStore(RoleStore):
35
+ """In-memory role storage for testing and development.
36
+
37
+ Thread-safe implementation using a dictionary backend.
38
+
39
+ Example:
40
+ >>> store = MemoryRoleStore()
41
+ >>> role = Role(id="admin", name="Administrator")
42
+ >>> store.save(role)
43
+ >>> retrieved = store.get("admin")
44
+ """
45
+
46
+ def __init__(self) -> None:
47
+ self._roles: dict[str, Role] = {}
48
+ self._lock = threading.RLock()
49
+
50
+ def get(self, role_id: str) -> Role | None:
51
+ """Get a role by ID."""
52
+ with self._lock:
53
+ return self._roles.get(role_id)
54
+
55
+ def list(
56
+ self,
57
+ tenant_id: str | None = None,
58
+ role_type: RoleType | None = None,
59
+ enabled: bool | None = None,
60
+ ) -> list[Role]:
61
+ """List roles with optional filters."""
62
+ with self._lock:
63
+ roles = list(self._roles.values())
64
+
65
+ if tenant_id is not None:
66
+ roles = [r for r in roles if r.tenant_id == tenant_id]
67
+ if role_type is not None:
68
+ roles = [r for r in roles if r.role_type == role_type]
69
+ if enabled is not None:
70
+ roles = [r for r in roles if r.enabled == enabled]
71
+
72
+ return sorted(roles, key=lambda r: r.name)
73
+
74
+ def save(self, role: Role) -> None:
75
+ """Save a role (create or update)."""
76
+ with self._lock:
77
+ role.updated_at = datetime.now(timezone.utc)
78
+ self._roles[role.id] = role
79
+
80
+ def delete(self, role_id: str) -> bool:
81
+ """Delete a role. Returns True if deleted."""
82
+ with self._lock:
83
+ if role_id in self._roles:
84
+ del self._roles[role_id]
85
+ return True
86
+ return False
87
+
88
+ def exists(self, role_id: str) -> bool:
89
+ """Check if a role exists."""
90
+ with self._lock:
91
+ return role_id in self._roles
92
+
93
+ def clear(self) -> None:
94
+ """Clear all roles (for testing)."""
95
+ with self._lock:
96
+ self._roles.clear()
97
+
98
+
99
+ class MemoryPrincipalStore(PrincipalStore):
100
+ """In-memory principal storage for testing and development.
101
+
102
+ Example:
103
+ >>> store = MemoryPrincipalStore()
104
+ >>> principal = Principal(id="user:123", name="John Doe")
105
+ >>> store.save(principal)
106
+ >>> retrieved = store.get("user:123")
107
+ """
108
+
109
+ def __init__(self) -> None:
110
+ self._principals: dict[str, Principal] = {}
111
+ self._email_index: dict[str, str] = {} # email -> principal_id
112
+ self._lock = threading.RLock()
113
+
114
+ def get(self, principal_id: str) -> Principal | None:
115
+ """Get a principal by ID."""
116
+ with self._lock:
117
+ return self._principals.get(principal_id)
118
+
119
+ def get_by_email(self, email: str) -> Principal | None:
120
+ """Get a principal by email."""
121
+ with self._lock:
122
+ principal_id = self._email_index.get(email.lower())
123
+ if principal_id:
124
+ return self._principals.get(principal_id)
125
+ return None
126
+
127
+ def list(
128
+ self,
129
+ tenant_id: str | None = None,
130
+ principal_type: PrincipalType | None = None,
131
+ role_id: str | None = None,
132
+ ) -> list[Principal]:
133
+ """List principals with optional filters."""
134
+ with self._lock:
135
+ principals = list(self._principals.values())
136
+
137
+ if tenant_id is not None:
138
+ principals = [p for p in principals if p.tenant_id == tenant_id]
139
+ if principal_type is not None:
140
+ principals = [p for p in principals if p.type == principal_type]
141
+ if role_id is not None:
142
+ principals = [p for p in principals if role_id in p.roles]
143
+
144
+ return sorted(principals, key=lambda p: p.name or p.id)
145
+
146
+ def save(self, principal: Principal) -> None:
147
+ """Save a principal (create or update)."""
148
+ with self._lock:
149
+ # Remove old email index
150
+ if principal.id in self._principals:
151
+ old = self._principals[principal.id]
152
+ if old.email and old.email.lower() in self._email_index:
153
+ del self._email_index[old.email.lower()]
154
+
155
+ # Save principal
156
+ self._principals[principal.id] = principal
157
+
158
+ # Update email index
159
+ if principal.email:
160
+ self._email_index[principal.email.lower()] = principal.id
161
+
162
+ def delete(self, principal_id: str) -> bool:
163
+ """Delete a principal. Returns True if deleted."""
164
+ with self._lock:
165
+ if principal_id in self._principals:
166
+ principal = self._principals[principal_id]
167
+ if principal.email and principal.email.lower() in self._email_index:
168
+ del self._email_index[principal.email.lower()]
169
+ del self._principals[principal_id]
170
+ return True
171
+ return False
172
+
173
+ def clear(self) -> None:
174
+ """Clear all principals (for testing)."""
175
+ with self._lock:
176
+ self._principals.clear()
177
+ self._email_index.clear()
178
+
179
+
180
+ # =============================================================================
181
+ # File-Based Storage
182
+ # =============================================================================
183
+
184
+
185
+ @dataclass
186
+ class FileStorageConfig:
187
+ """Configuration for file-based RBAC storage."""
188
+
189
+ base_path: str | Path = ".truthound/rbac"
190
+ roles_file: str = "roles.json"
191
+ principals_file: str = "principals.json"
192
+ create_dirs: bool = True
193
+ pretty_print: bool = True
194
+
195
+
196
+ class FileRoleStore(RoleStore):
197
+ """File-based role storage.
198
+
199
+ Stores roles in a JSON file.
200
+
201
+ Example:
202
+ >>> store = FileRoleStore(config=FileStorageConfig(base_path="/tmp/rbac"))
203
+ >>> store.save(Role(id="admin", name="Administrator"))
204
+ """
205
+
206
+ def __init__(self, config: FileStorageConfig | None = None) -> None:
207
+ self._config = config or FileStorageConfig()
208
+ self._base_path = Path(self._config.base_path)
209
+ self._file_path = self._base_path / self._config.roles_file
210
+ self._lock = threading.RLock()
211
+ self._cache: dict[str, Role] = {}
212
+ self._cache_loaded = False
213
+
214
+ if self._config.create_dirs:
215
+ self._base_path.mkdir(parents=True, exist_ok=True)
216
+
217
+ def _load_cache(self) -> None:
218
+ """Load roles from file into cache."""
219
+ if self._cache_loaded:
220
+ return
221
+
222
+ if self._file_path.exists():
223
+ try:
224
+ with open(self._file_path) as f:
225
+ data = json.load(f)
226
+ self._cache = {
227
+ role_id: Role.from_dict(role_data)
228
+ for role_id, role_data in data.items()
229
+ }
230
+ except (json.JSONDecodeError, OSError):
231
+ self._cache = {}
232
+
233
+ self._cache_loaded = True
234
+
235
+ def _save_cache(self) -> None:
236
+ """Save cache to file."""
237
+ data = {role_id: role.to_dict() for role_id, role in self._cache.items()}
238
+ with open(self._file_path, "w") as f:
239
+ json.dump(data, f, indent=2 if self._config.pretty_print else None, default=str)
240
+
241
+ def get(self, role_id: str) -> Role | None:
242
+ """Get a role by ID."""
243
+ with self._lock:
244
+ self._load_cache()
245
+ return self._cache.get(role_id)
246
+
247
+ def list(
248
+ self,
249
+ tenant_id: str | None = None,
250
+ role_type: RoleType | None = None,
251
+ enabled: bool | None = None,
252
+ ) -> list[Role]:
253
+ """List roles with optional filters."""
254
+ with self._lock:
255
+ self._load_cache()
256
+ roles = list(self._cache.values())
257
+
258
+ if tenant_id is not None:
259
+ roles = [r for r in roles if r.tenant_id == tenant_id]
260
+ if role_type is not None:
261
+ roles = [r for r in roles if r.role_type == role_type]
262
+ if enabled is not None:
263
+ roles = [r for r in roles if r.enabled == enabled]
264
+
265
+ return sorted(roles, key=lambda r: r.name)
266
+
267
+ def save(self, role: Role) -> None:
268
+ """Save a role (create or update)."""
269
+ with self._lock:
270
+ self._load_cache()
271
+ role.updated_at = datetime.now(timezone.utc)
272
+ self._cache[role.id] = role
273
+ self._save_cache()
274
+
275
+ def delete(self, role_id: str) -> bool:
276
+ """Delete a role. Returns True if deleted."""
277
+ with self._lock:
278
+ self._load_cache()
279
+ if role_id in self._cache:
280
+ del self._cache[role_id]
281
+ self._save_cache()
282
+ return True
283
+ return False
284
+
285
+ def exists(self, role_id: str) -> bool:
286
+ """Check if a role exists."""
287
+ with self._lock:
288
+ self._load_cache()
289
+ return role_id in self._cache
290
+
291
+
292
+ class FilePrincipalStore(PrincipalStore):
293
+ """File-based principal storage.
294
+
295
+ Stores principals in a JSON file.
296
+ """
297
+
298
+ def __init__(self, config: FileStorageConfig | None = None) -> None:
299
+ self._config = config or FileStorageConfig()
300
+ self._base_path = Path(self._config.base_path)
301
+ self._file_path = self._base_path / self._config.principals_file
302
+ self._lock = threading.RLock()
303
+ self._cache: dict[str, Principal] = {}
304
+ self._email_index: dict[str, str] = {}
305
+ self._cache_loaded = False
306
+
307
+ if self._config.create_dirs:
308
+ self._base_path.mkdir(parents=True, exist_ok=True)
309
+
310
+ def _load_cache(self) -> None:
311
+ """Load principals from file into cache."""
312
+ if self._cache_loaded:
313
+ return
314
+
315
+ if self._file_path.exists():
316
+ try:
317
+ with open(self._file_path) as f:
318
+ data = json.load(f)
319
+ self._cache = {
320
+ principal_id: Principal.from_dict(principal_data)
321
+ for principal_id, principal_data in data.items()
322
+ }
323
+ # Rebuild email index
324
+ self._email_index = {
325
+ p.email.lower(): p.id
326
+ for p in self._cache.values()
327
+ if p.email
328
+ }
329
+ except (json.JSONDecodeError, OSError):
330
+ self._cache = {}
331
+
332
+ self._cache_loaded = True
333
+
334
+ def _save_cache(self) -> None:
335
+ """Save cache to file."""
336
+ data = {
337
+ principal_id: principal.to_dict()
338
+ for principal_id, principal in self._cache.items()
339
+ }
340
+ with open(self._file_path, "w") as f:
341
+ json.dump(data, f, indent=2 if self._config.pretty_print else None, default=str)
342
+
343
+ def get(self, principal_id: str) -> Principal | None:
344
+ """Get a principal by ID."""
345
+ with self._lock:
346
+ self._load_cache()
347
+ return self._cache.get(principal_id)
348
+
349
+ def get_by_email(self, email: str) -> Principal | None:
350
+ """Get a principal by email."""
351
+ with self._lock:
352
+ self._load_cache()
353
+ principal_id = self._email_index.get(email.lower())
354
+ if principal_id:
355
+ return self._cache.get(principal_id)
356
+ return None
357
+
358
+ def list(
359
+ self,
360
+ tenant_id: str | None = None,
361
+ principal_type: PrincipalType | None = None,
362
+ role_id: str | None = None,
363
+ ) -> list[Principal]:
364
+ """List principals with optional filters."""
365
+ with self._lock:
366
+ self._load_cache()
367
+ principals = list(self._cache.values())
368
+
369
+ if tenant_id is not None:
370
+ principals = [p for p in principals if p.tenant_id == tenant_id]
371
+ if principal_type is not None:
372
+ principals = [p for p in principals if p.type == principal_type]
373
+ if role_id is not None:
374
+ principals = [p for p in principals if role_id in p.roles]
375
+
376
+ return sorted(principals, key=lambda p: p.name or p.id)
377
+
378
+ def save(self, principal: Principal) -> None:
379
+ """Save a principal (create or update)."""
380
+ with self._lock:
381
+ self._load_cache()
382
+
383
+ # Update email index
384
+ if principal.id in self._cache:
385
+ old = self._cache[principal.id]
386
+ if old.email and old.email.lower() in self._email_index:
387
+ del self._email_index[old.email.lower()]
388
+
389
+ self._cache[principal.id] = principal
390
+ if principal.email:
391
+ self._email_index[principal.email.lower()] = principal.id
392
+
393
+ self._save_cache()
394
+
395
+ def delete(self, principal_id: str) -> bool:
396
+ """Delete a principal. Returns True if deleted."""
397
+ with self._lock:
398
+ self._load_cache()
399
+ if principal_id in self._cache:
400
+ principal = self._cache[principal_id]
401
+ if principal.email and principal.email.lower() in self._email_index:
402
+ del self._email_index[principal.email.lower()]
403
+ del self._cache[principal_id]
404
+ self._save_cache()
405
+ return True
406
+ return False
407
+
408
+
409
+ # =============================================================================
410
+ # SQLite Storage
411
+ # =============================================================================
412
+
413
+
414
+ class SQLiteRoleStore(RoleStore):
415
+ """SQLite-based role storage.
416
+
417
+ Example:
418
+ >>> store = SQLiteRoleStore(db_path="/tmp/rbac.db")
419
+ >>> store.save(Role(id="admin", name="Administrator"))
420
+ """
421
+
422
+ def __init__(
423
+ self,
424
+ db_path: str | Path = ".truthound/rbac.db",
425
+ create_tables: bool = True,
426
+ ) -> None:
427
+ import sqlite3
428
+
429
+ self._db_path = Path(db_path)
430
+ self._db_path.parent.mkdir(parents=True, exist_ok=True)
431
+
432
+ self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
433
+ self._conn.row_factory = sqlite3.Row
434
+ self._lock = threading.RLock()
435
+
436
+ if create_tables:
437
+ self._create_tables()
438
+
439
+ def _create_tables(self) -> None:
440
+ """Create the roles table."""
441
+ with self._lock:
442
+ cursor = self._conn.cursor()
443
+ cursor.execute("""
444
+ CREATE TABLE IF NOT EXISTS roles (
445
+ id TEXT PRIMARY KEY,
446
+ name TEXT NOT NULL,
447
+ description TEXT DEFAULT '',
448
+ role_type TEXT NOT NULL DEFAULT 'custom',
449
+ permissions_json TEXT DEFAULT '[]',
450
+ parent_roles_json TEXT DEFAULT '[]',
451
+ conditions_json TEXT DEFAULT '[]',
452
+ tenant_id TEXT,
453
+ created_at TEXT NOT NULL,
454
+ updated_at TEXT NOT NULL,
455
+ created_by TEXT DEFAULT '',
456
+ metadata_json TEXT DEFAULT '{}',
457
+ enabled INTEGER DEFAULT 1
458
+ )
459
+ """)
460
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_roles_tenant ON roles(tenant_id)")
461
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_roles_type ON roles(role_type)")
462
+ self._conn.commit()
463
+
464
+ def _row_to_role(self, row: Any) -> Role:
465
+ """Convert a database row to a Role object."""
466
+ permissions = json.loads(row["permissions_json"]) if row["permissions_json"] else []
467
+ parent_roles = json.loads(row["parent_roles_json"]) if row["parent_roles_json"] else []
468
+ metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
469
+
470
+ return Role(
471
+ id=row["id"],
472
+ name=row["name"],
473
+ description=row["description"] or "",
474
+ role_type=RoleType(row["role_type"]),
475
+ permissions={Permission.parse(p) for p in permissions},
476
+ parent_roles=set(parent_roles),
477
+ tenant_id=row["tenant_id"],
478
+ created_at=datetime.fromisoformat(row["created_at"]),
479
+ updated_at=datetime.fromisoformat(row["updated_at"]),
480
+ created_by=row["created_by"] or "",
481
+ metadata=metadata,
482
+ enabled=bool(row["enabled"]),
483
+ )
484
+
485
+ def get(self, role_id: str) -> Role | None:
486
+ """Get a role by ID."""
487
+ with self._lock:
488
+ cursor = self._conn.cursor()
489
+ cursor.execute("SELECT * FROM roles WHERE id = ?", (role_id,))
490
+ row = cursor.fetchone()
491
+ if row:
492
+ return self._row_to_role(row)
493
+ return None
494
+
495
+ def list(
496
+ self,
497
+ tenant_id: str | None = None,
498
+ role_type: RoleType | None = None,
499
+ enabled: bool | None = None,
500
+ ) -> list[Role]:
501
+ """List roles with optional filters."""
502
+ with self._lock:
503
+ query = "SELECT * FROM roles WHERE 1=1"
504
+ params: list[Any] = []
505
+
506
+ if tenant_id is not None:
507
+ query += " AND tenant_id = ?"
508
+ params.append(tenant_id)
509
+ if role_type is not None:
510
+ query += " AND role_type = ?"
511
+ params.append(role_type.value)
512
+ if enabled is not None:
513
+ query += " AND enabled = ?"
514
+ params.append(1 if enabled else 0)
515
+
516
+ query += " ORDER BY name"
517
+
518
+ cursor = self._conn.cursor()
519
+ cursor.execute(query, params)
520
+ return [self._row_to_role(row) for row in cursor.fetchall()]
521
+
522
+ def save(self, role: Role) -> None:
523
+ """Save a role (create or update)."""
524
+ with self._lock:
525
+ role.updated_at = datetime.now(timezone.utc)
526
+ cursor = self._conn.cursor()
527
+ cursor.execute(
528
+ """
529
+ INSERT OR REPLACE INTO roles (
530
+ id, name, description, role_type,
531
+ permissions_json, parent_roles_json, conditions_json,
532
+ tenant_id, created_at, updated_at, created_by,
533
+ metadata_json, enabled
534
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
535
+ """,
536
+ (
537
+ role.id,
538
+ role.name,
539
+ role.description,
540
+ role.role_type.value,
541
+ json.dumps([p.to_string() for p in role.permissions]),
542
+ json.dumps(list(role.parent_roles)),
543
+ json.dumps([c.to_dict() for c in role.conditions]),
544
+ role.tenant_id,
545
+ role.created_at.isoformat(),
546
+ role.updated_at.isoformat(),
547
+ role.created_by,
548
+ json.dumps(role.metadata),
549
+ 1 if role.enabled else 0,
550
+ ),
551
+ )
552
+ self._conn.commit()
553
+
554
+ def delete(self, role_id: str) -> bool:
555
+ """Delete a role. Returns True if deleted."""
556
+ with self._lock:
557
+ cursor = self._conn.cursor()
558
+ cursor.execute("DELETE FROM roles WHERE id = ?", (role_id,))
559
+ self._conn.commit()
560
+ return cursor.rowcount > 0
561
+
562
+ def exists(self, role_id: str) -> bool:
563
+ """Check if a role exists."""
564
+ with self._lock:
565
+ cursor = self._conn.cursor()
566
+ cursor.execute("SELECT 1 FROM roles WHERE id = ?", (role_id,))
567
+ return cursor.fetchone() is not None
568
+
569
+ def close(self) -> None:
570
+ """Close the database connection."""
571
+ self._conn.close()
572
+
573
+
574
+ class SQLitePrincipalStore(PrincipalStore):
575
+ """SQLite-based principal storage."""
576
+
577
+ def __init__(
578
+ self,
579
+ db_path: str | Path = ".truthound/rbac.db",
580
+ create_tables: bool = True,
581
+ ) -> None:
582
+ import sqlite3
583
+
584
+ self._db_path = Path(db_path)
585
+ self._db_path.parent.mkdir(parents=True, exist_ok=True)
586
+
587
+ self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
588
+ self._conn.row_factory = sqlite3.Row
589
+ self._lock = threading.RLock()
590
+
591
+ if create_tables:
592
+ self._create_tables()
593
+
594
+ def _create_tables(self) -> None:
595
+ """Create the principals table."""
596
+ with self._lock:
597
+ cursor = self._conn.cursor()
598
+ cursor.execute("""
599
+ CREATE TABLE IF NOT EXISTS principals (
600
+ id TEXT PRIMARY KEY,
601
+ type TEXT NOT NULL DEFAULT 'user',
602
+ name TEXT DEFAULT '',
603
+ email TEXT DEFAULT '',
604
+ roles_json TEXT DEFAULT '[]',
605
+ direct_permissions_json TEXT DEFAULT '[]',
606
+ attributes_json TEXT DEFAULT '{}',
607
+ tenant_id TEXT,
608
+ enabled INTEGER DEFAULT 1,
609
+ created_at TEXT NOT NULL,
610
+ last_active_at TEXT,
611
+ metadata_json TEXT DEFAULT '{}'
612
+ )
613
+ """)
614
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_principals_email ON principals(email)")
615
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_principals_tenant ON principals(tenant_id)")
616
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_principals_type ON principals(type)")
617
+ self._conn.commit()
618
+
619
+ def _row_to_principal(self, row: Any) -> Principal:
620
+ """Convert a database row to a Principal object."""
621
+ roles = json.loads(row["roles_json"]) if row["roles_json"] else []
622
+ direct_permissions = json.loads(row["direct_permissions_json"]) if row["direct_permissions_json"] else []
623
+ attributes = json.loads(row["attributes_json"]) if row["attributes_json"] else {}
624
+ metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
625
+
626
+ return Principal(
627
+ id=row["id"],
628
+ type=PrincipalType(row["type"]),
629
+ name=row["name"] or "",
630
+ email=row["email"] or "",
631
+ roles=set(roles),
632
+ direct_permissions={Permission.parse(p) for p in direct_permissions},
633
+ attributes=attributes,
634
+ tenant_id=row["tenant_id"],
635
+ enabled=bool(row["enabled"]),
636
+ created_at=datetime.fromisoformat(row["created_at"]),
637
+ last_active_at=datetime.fromisoformat(row["last_active_at"]) if row["last_active_at"] else None,
638
+ metadata=metadata,
639
+ )
640
+
641
+ def get(self, principal_id: str) -> Principal | None:
642
+ """Get a principal by ID."""
643
+ with self._lock:
644
+ cursor = self._conn.cursor()
645
+ cursor.execute("SELECT * FROM principals WHERE id = ?", (principal_id,))
646
+ row = cursor.fetchone()
647
+ if row:
648
+ return self._row_to_principal(row)
649
+ return None
650
+
651
+ def get_by_email(self, email: str) -> Principal | None:
652
+ """Get a principal by email."""
653
+ with self._lock:
654
+ cursor = self._conn.cursor()
655
+ cursor.execute(
656
+ "SELECT * FROM principals WHERE LOWER(email) = LOWER(?)",
657
+ (email,),
658
+ )
659
+ row = cursor.fetchone()
660
+ if row:
661
+ return self._row_to_principal(row)
662
+ return None
663
+
664
+ def list(
665
+ self,
666
+ tenant_id: str | None = None,
667
+ principal_type: PrincipalType | None = None,
668
+ role_id: str | None = None,
669
+ ) -> list[Principal]:
670
+ """List principals with optional filters."""
671
+ with self._lock:
672
+ query = "SELECT * FROM principals WHERE 1=1"
673
+ params: list[Any] = []
674
+
675
+ if tenant_id is not None:
676
+ query += " AND tenant_id = ?"
677
+ params.append(tenant_id)
678
+ if principal_type is not None:
679
+ query += " AND type = ?"
680
+ params.append(principal_type.value)
681
+
682
+ query += " ORDER BY name"
683
+
684
+ cursor = self._conn.cursor()
685
+ cursor.execute(query, params)
686
+ principals = [self._row_to_principal(row) for row in cursor.fetchall()]
687
+
688
+ # Filter by role (needs to check JSON)
689
+ if role_id is not None:
690
+ principals = [p for p in principals if role_id in p.roles]
691
+
692
+ return principals
693
+
694
+ def save(self, principal: Principal) -> None:
695
+ """Save a principal (create or update)."""
696
+ with self._lock:
697
+ cursor = self._conn.cursor()
698
+ cursor.execute(
699
+ """
700
+ INSERT OR REPLACE INTO principals (
701
+ id, type, name, email, roles_json, direct_permissions_json,
702
+ attributes_json, tenant_id, enabled, created_at, last_active_at,
703
+ metadata_json
704
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
705
+ """,
706
+ (
707
+ principal.id,
708
+ principal.type.value,
709
+ principal.name,
710
+ principal.email,
711
+ json.dumps(list(principal.roles)),
712
+ json.dumps([p.to_string() for p in principal.direct_permissions]),
713
+ json.dumps(principal.attributes),
714
+ principal.tenant_id,
715
+ 1 if principal.enabled else 0,
716
+ principal.created_at.isoformat(),
717
+ principal.last_active_at.isoformat() if principal.last_active_at else None,
718
+ json.dumps(principal.metadata),
719
+ ),
720
+ )
721
+ self._conn.commit()
722
+
723
+ def delete(self, principal_id: str) -> bool:
724
+ """Delete a principal. Returns True if deleted."""
725
+ with self._lock:
726
+ cursor = self._conn.cursor()
727
+ cursor.execute("DELETE FROM principals WHERE id = ?", (principal_id,))
728
+ self._conn.commit()
729
+ return cursor.rowcount > 0
730
+
731
+ def close(self) -> None:
732
+ """Close the database connection."""
733
+ self._conn.close()
734
+
735
+
736
+ # =============================================================================
737
+ # Cached Storage Wrapper
738
+ # =============================================================================
739
+
740
+
741
+ @dataclass
742
+ class CacheConfig:
743
+ """Configuration for caching."""
744
+
745
+ ttl_seconds: int = 300 # 5 minutes
746
+ max_size: int = 1000
747
+ refresh_on_access: bool = True
748
+
749
+
750
+ class CachedRoleStore(RoleStore):
751
+ """Caching wrapper for any RoleStore."""
752
+
753
+ def __init__(
754
+ self,
755
+ backend: RoleStore,
756
+ cache_config: CacheConfig | None = None,
757
+ ) -> None:
758
+ self._backend = backend
759
+ self._config = cache_config or CacheConfig()
760
+ self._cache: dict[str, tuple[Role, float]] = {}
761
+ self._lock = threading.RLock()
762
+
763
+ def _is_expired(self, cached_at: float) -> bool:
764
+ return time.time() - cached_at > self._config.ttl_seconds
765
+
766
+ def get(self, role_id: str) -> Role | None:
767
+ with self._lock:
768
+ if role_id in self._cache:
769
+ role, cached_at = self._cache[role_id]
770
+ if not self._is_expired(cached_at):
771
+ if self._config.refresh_on_access:
772
+ self._cache[role_id] = (role, time.time())
773
+ return role
774
+ else:
775
+ del self._cache[role_id]
776
+
777
+ role = self._backend.get(role_id)
778
+ if role:
779
+ with self._lock:
780
+ self._cache[role_id] = (role, time.time())
781
+ return role
782
+
783
+ def list(
784
+ self,
785
+ tenant_id: str | None = None,
786
+ role_type: RoleType | None = None,
787
+ enabled: bool | None = None,
788
+ ) -> list[Role]:
789
+ # List always goes to backend
790
+ roles = self._backend.list(
791
+ tenant_id=tenant_id,
792
+ role_type=role_type,
793
+ enabled=enabled,
794
+ )
795
+ # Update cache
796
+ with self._lock:
797
+ for role in roles:
798
+ self._cache[role.id] = (role, time.time())
799
+ return roles
800
+
801
+ def save(self, role: Role) -> None:
802
+ with self._lock:
803
+ if role.id in self._cache:
804
+ del self._cache[role.id]
805
+ self._backend.save(role)
806
+ with self._lock:
807
+ self._cache[role.id] = (role, time.time())
808
+
809
+ def delete(self, role_id: str) -> bool:
810
+ with self._lock:
811
+ if role_id in self._cache:
812
+ del self._cache[role_id]
813
+ return self._backend.delete(role_id)
814
+
815
+ def exists(self, role_id: str) -> bool:
816
+ return self.get(role_id) is not None
817
+
818
+ def invalidate(self, role_id: str | None = None) -> None:
819
+ """Invalidate cache entries."""
820
+ with self._lock:
821
+ if role_id:
822
+ if role_id in self._cache:
823
+ del self._cache[role_id]
824
+ else:
825
+ self._cache.clear()
826
+
827
+
828
+ # =============================================================================
829
+ # Factory Functions
830
+ # =============================================================================
831
+
832
+
833
+ def create_role_store(
834
+ backend: str = "memory",
835
+ **kwargs: Any,
836
+ ) -> RoleStore:
837
+ """Create a role store.
838
+
839
+ Args:
840
+ backend: Storage backend ("memory", "file", "sqlite")
841
+ **kwargs: Backend-specific configuration
842
+
843
+ Returns:
844
+ Configured RoleStore instance.
845
+ """
846
+ if backend == "memory":
847
+ return MemoryRoleStore()
848
+ elif backend == "file":
849
+ config = FileStorageConfig(**kwargs)
850
+ return FileRoleStore(config=config)
851
+ elif backend == "sqlite":
852
+ return SQLiteRoleStore(**kwargs)
853
+ else:
854
+ raise ValueError(f"Unknown backend: {backend}")
855
+
856
+
857
+ def create_principal_store(
858
+ backend: str = "memory",
859
+ **kwargs: Any,
860
+ ) -> PrincipalStore:
861
+ """Create a principal store.
862
+
863
+ Args:
864
+ backend: Storage backend ("memory", "file", "sqlite")
865
+ **kwargs: Backend-specific configuration
866
+
867
+ Returns:
868
+ Configured PrincipalStore instance.
869
+ """
870
+ if backend == "memory":
871
+ return MemoryPrincipalStore()
872
+ elif backend == "file":
873
+ config = FileStorageConfig(**kwargs)
874
+ return FilePrincipalStore(config=config)
875
+ elif backend == "sqlite":
876
+ return SQLitePrincipalStore(**kwargs)
877
+ else:
878
+ raise ValueError(f"Unknown backend: {backend}")