PraisonAI 3.0.0__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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,364 @@
1
+ """
2
+ Template Cache
3
+
4
+ Disk-based caching for templates with TTL support.
5
+ Pinned versions are cached indefinitely, 'latest' has configurable TTL.
6
+ """
7
+
8
+ import hashlib
9
+ import json
10
+ import shutil
11
+ import time
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any, Dict, Optional
15
+
16
+ from .resolver import ResolvedTemplate, TemplateSource
17
+
18
+
19
+ @dataclass
20
+ class CacheMetadata:
21
+ """Metadata for a cached template."""
22
+ fetched_at: float
23
+ etag: Optional[str] = None
24
+ sha256: Optional[str] = None
25
+ ttl_seconds: int = 86400 # 24 hours default
26
+ source_url: Optional[str] = None
27
+ version: Optional[str] = None
28
+ is_pinned: bool = False
29
+
30
+ def is_expired(self) -> bool:
31
+ """Check if the cache entry has expired."""
32
+ if self.is_pinned:
33
+ return False # Pinned versions never expire
34
+ return time.time() > (self.fetched_at + self.ttl_seconds)
35
+
36
+ def to_dict(self) -> Dict[str, Any]:
37
+ """Convert to dictionary for JSON serialization."""
38
+ return {
39
+ "fetched_at": self.fetched_at,
40
+ "etag": self.etag,
41
+ "sha256": self.sha256,
42
+ "ttl_seconds": self.ttl_seconds,
43
+ "source_url": self.source_url,
44
+ "version": self.version,
45
+ "is_pinned": self.is_pinned,
46
+ }
47
+
48
+ @classmethod
49
+ def from_dict(cls, data: Dict[str, Any]) -> "CacheMetadata":
50
+ """Create from dictionary."""
51
+ return cls(
52
+ fetched_at=data.get("fetched_at", 0),
53
+ etag=data.get("etag"),
54
+ sha256=data.get("sha256"),
55
+ ttl_seconds=data.get("ttl_seconds", 86400),
56
+ source_url=data.get("source_url"),
57
+ version=data.get("version"),
58
+ is_pinned=data.get("is_pinned", False),
59
+ )
60
+
61
+
62
+ @dataclass
63
+ class CachedTemplate:
64
+ """A cached template with its metadata."""
65
+ path: Path
66
+ metadata: CacheMetadata
67
+ config: Dict[str, Any] = field(default_factory=dict)
68
+
69
+
70
+ class TemplateCache:
71
+ """
72
+ Disk-based template cache with TTL support.
73
+
74
+ Cache structure:
75
+ ~/.praison/cache/templates/
76
+ ├── github/
77
+ │ └── owner/
78
+ │ └── repo/
79
+ │ └── template-name/
80
+ │ └── ref/
81
+ │ ├── TEMPLATE.yaml
82
+ │ ├── workflow.yaml
83
+ │ └── .cache_meta.json
84
+ ├── package/
85
+ │ └── package_name/
86
+ │ └── template-name/
87
+ └── http/
88
+ └── hash-of-url/
89
+ """
90
+
91
+ DEFAULT_CACHE_DIR = Path.home() / ".praison" / "cache" / "templates"
92
+ DEFAULT_TTL = 86400 # 24 hours
93
+ METADATA_FILE = ".cache_meta.json"
94
+
95
+ def __init__(
96
+ self,
97
+ cache_dir: Optional[Path] = None,
98
+ default_ttl: int = DEFAULT_TTL
99
+ ):
100
+ """
101
+ Initialize the template cache.
102
+
103
+ Args:
104
+ cache_dir: Custom cache directory (default: ~/.praison/cache/templates)
105
+ default_ttl: Default TTL in seconds for non-pinned templates
106
+ """
107
+ self.cache_dir = cache_dir or self.DEFAULT_CACHE_DIR
108
+ self.default_ttl = default_ttl
109
+ self._ensure_cache_dir()
110
+
111
+ def _ensure_cache_dir(self) -> None:
112
+ """Ensure the cache directory exists."""
113
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
114
+
115
+ def _get_cache_path(self, resolved: ResolvedTemplate) -> Path:
116
+ """Get the cache path for a resolved template."""
117
+ if resolved.source == TemplateSource.LOCAL:
118
+ # Local templates are not cached
119
+ return Path(resolved.path)
120
+
121
+ elif resolved.source == TemplateSource.PACKAGE:
122
+ # package/package_name/template
123
+ return self.cache_dir / "package" / resolved.path
124
+
125
+ elif resolved.source == TemplateSource.GITHUB:
126
+ # github/owner/repo/template/ref
127
+ ref = resolved.ref or "main"
128
+ return (
129
+ self.cache_dir / "github" /
130
+ resolved.owner / resolved.repo /
131
+ resolved.path / ref
132
+ )
133
+
134
+ elif resolved.source == TemplateSource.HTTP:
135
+ # http/hash-of-url
136
+ url_hash = hashlib.sha256(resolved.url.encode()).hexdigest()[:16]
137
+ return self.cache_dir / "http" / url_hash
138
+
139
+ raise ValueError(f"Unknown template source: {resolved.source}")
140
+
141
+ def get(
142
+ self,
143
+ resolved: ResolvedTemplate,
144
+ offline: bool = False
145
+ ) -> Optional[CachedTemplate]:
146
+ """
147
+ Get a cached template if it exists and is valid.
148
+
149
+ Args:
150
+ resolved: Resolved template reference
151
+ offline: If True, return cached version even if expired
152
+
153
+ Returns:
154
+ CachedTemplate if found and valid, None otherwise
155
+ """
156
+ if resolved.source == TemplateSource.LOCAL:
157
+ # Local templates are not cached, return path directly
158
+ path = Path(resolved.path)
159
+ if path.exists():
160
+ return CachedTemplate(
161
+ path=path,
162
+ metadata=CacheMetadata(
163
+ fetched_at=time.time(),
164
+ is_pinned=True
165
+ )
166
+ )
167
+ return None
168
+
169
+ cache_path = self._get_cache_path(resolved)
170
+ meta_path = cache_path / self.METADATA_FILE
171
+
172
+ if not cache_path.exists() or not meta_path.exists():
173
+ return None
174
+
175
+ # Load metadata
176
+ try:
177
+ with open(meta_path, "r") as f:
178
+ metadata = CacheMetadata.from_dict(json.load(f))
179
+ except (json.JSONDecodeError, IOError):
180
+ return None
181
+
182
+ # Check expiration (unless offline mode)
183
+ if not offline and metadata.is_expired():
184
+ return None
185
+
186
+ return CachedTemplate(
187
+ path=cache_path,
188
+ metadata=metadata
189
+ )
190
+
191
+ def put(
192
+ self,
193
+ resolved: ResolvedTemplate,
194
+ content_dir: Path,
195
+ etag: Optional[str] = None,
196
+ sha256: Optional[str] = None
197
+ ) -> CachedTemplate:
198
+ """
199
+ Store a template in the cache.
200
+
201
+ Args:
202
+ resolved: Resolved template reference
203
+ content_dir: Directory containing template files to cache
204
+ etag: Optional ETag for cache validation
205
+ sha256: Optional SHA256 checksum
206
+
207
+ Returns:
208
+ CachedTemplate with the cached location
209
+ """
210
+ if resolved.source == TemplateSource.LOCAL:
211
+ # Don't cache local templates
212
+ return CachedTemplate(
213
+ path=Path(resolved.path),
214
+ metadata=CacheMetadata(
215
+ fetched_at=time.time(),
216
+ is_pinned=True
217
+ )
218
+ )
219
+
220
+ cache_path = self._get_cache_path(resolved)
221
+
222
+ # Remove existing cache entry
223
+ if cache_path.exists():
224
+ shutil.rmtree(cache_path)
225
+
226
+ # Copy content to cache
227
+ cache_path.mkdir(parents=True, exist_ok=True)
228
+
229
+ if content_dir.is_dir():
230
+ for item in content_dir.iterdir():
231
+ if item.is_file():
232
+ shutil.copy2(item, cache_path / item.name)
233
+ elif item.is_dir():
234
+ shutil.copytree(item, cache_path / item.name)
235
+ else:
236
+ # Single file
237
+ shutil.copy2(content_dir, cache_path / content_dir.name)
238
+
239
+ # Create metadata
240
+ metadata = CacheMetadata(
241
+ fetched_at=time.time(),
242
+ etag=etag,
243
+ sha256=sha256,
244
+ ttl_seconds=self.default_ttl,
245
+ source_url=resolved.url if resolved.source == TemplateSource.HTTP else None,
246
+ version=resolved.ref,
247
+ is_pinned=resolved.is_pinned
248
+ )
249
+
250
+ # Save metadata
251
+ meta_path = cache_path / self.METADATA_FILE
252
+ with open(meta_path, "w") as f:
253
+ json.dump(metadata.to_dict(), f, indent=2)
254
+
255
+ return CachedTemplate(
256
+ path=cache_path,
257
+ metadata=metadata
258
+ )
259
+
260
+ def invalidate(self, resolved: ResolvedTemplate) -> bool:
261
+ """
262
+ Invalidate a cached template.
263
+
264
+ Args:
265
+ resolved: Resolved template reference
266
+
267
+ Returns:
268
+ True if cache was invalidated, False if not found
269
+ """
270
+ cache_path = self._get_cache_path(resolved)
271
+ if cache_path.exists():
272
+ shutil.rmtree(cache_path)
273
+ return True
274
+ return False
275
+
276
+ def clear(self, source: Optional[TemplateSource] = None) -> int:
277
+ """
278
+ Clear the cache.
279
+
280
+ Args:
281
+ source: If specified, only clear templates from this source
282
+
283
+ Returns:
284
+ Number of templates cleared
285
+ """
286
+ count = 0
287
+
288
+ if source is None:
289
+ # Clear everything
290
+ if self.cache_dir.exists():
291
+ for item in self.cache_dir.iterdir():
292
+ if item.is_dir():
293
+ count += sum(1 for _ in item.rglob(self.METADATA_FILE))
294
+ shutil.rmtree(item)
295
+ else:
296
+ # Clear specific source
297
+ source_dir = self.cache_dir / source.value
298
+ if source_dir.exists():
299
+ count = sum(1 for _ in source_dir.rglob(self.METADATA_FILE))
300
+ shutil.rmtree(source_dir)
301
+
302
+ return count
303
+
304
+ def list_cached(
305
+ self,
306
+ source: Optional[TemplateSource] = None
307
+ ) -> list:
308
+ """
309
+ List all cached templates.
310
+
311
+ Args:
312
+ source: If specified, only list templates from this source
313
+
314
+ Returns:
315
+ List of (cache_path, metadata) tuples
316
+ """
317
+ results = []
318
+
319
+ search_dirs = []
320
+ if source is None:
321
+ search_dirs = [
322
+ self.cache_dir / s.value
323
+ for s in TemplateSource
324
+ if s != TemplateSource.LOCAL
325
+ ]
326
+ else:
327
+ search_dirs = [self.cache_dir / source.value]
328
+
329
+ for search_dir in search_dirs:
330
+ if not search_dir.exists():
331
+ continue
332
+
333
+ for meta_path in search_dir.rglob(self.METADATA_FILE):
334
+ try:
335
+ with open(meta_path, "r") as f:
336
+ metadata = CacheMetadata.from_dict(json.load(f))
337
+ results.append((meta_path.parent, metadata))
338
+ except (json.JSONDecodeError, IOError):
339
+ continue
340
+
341
+ return results
342
+
343
+ def get_cache_size(self) -> int:
344
+ """Get total cache size in bytes."""
345
+ if not self.cache_dir.exists():
346
+ return 0
347
+ return sum(f.stat().st_size for f in self.cache_dir.rglob("*") if f.is_file())
348
+
349
+
350
+ def clear_cache(source: Optional[str] = None) -> int:
351
+ """
352
+ Convenience function to clear the template cache.
353
+
354
+ Args:
355
+ source: Optional source type ('github', 'package', 'http')
356
+
357
+ Returns:
358
+ Number of templates cleared
359
+ """
360
+ cache = TemplateCache()
361
+ source_enum = None
362
+ if source:
363
+ source_enum = TemplateSource(source)
364
+ return cache.clear(source_enum)