pf-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. pf_core-0.1.0/LICENSE +21 -0
  2. pf_core-0.1.0/PKG-INFO +152 -0
  3. pf_core-0.1.0/README.md +55 -0
  4. pf_core-0.1.0/pyproject.toml +273 -0
  5. pf_core-0.1.0/setup.cfg +4 -0
  6. pf_core-0.1.0/src/pf_core/__init__.py +20 -0
  7. pf_core-0.1.0/src/pf_core/_extras.py +64 -0
  8. pf_core-0.1.0/src/pf_core/alembic.py +72 -0
  9. pf_core-0.1.0/src/pf_core/budget/__init__.py +147 -0
  10. pf_core-0.1.0/src/pf_core/budget/_schema.py +122 -0
  11. pf_core-0.1.0/src/pf_core/budget/audit.py +73 -0
  12. pf_core-0.1.0/src/pf_core/budget/check.py +369 -0
  13. pf_core-0.1.0/src/pf_core/budget/config.py +155 -0
  14. pf_core-0.1.0/src/pf_core/budget/repo.py +386 -0
  15. pf_core-0.1.0/src/pf_core/budget/scheduler.py +72 -0
  16. pf_core-0.1.0/src/pf_core/budget/snapshot_job.py +54 -0
  17. pf_core-0.1.0/src/pf_core/cache/__init__.py +0 -0
  18. pf_core-0.1.0/src/pf_core/cache/redis.py +198 -0
  19. pf_core-0.1.0/src/pf_core/cli/__init__.py +103 -0
  20. pf_core-0.1.0/src/pf_core/cli/jobs.py +272 -0
  21. pf_core-0.1.0/src/pf_core/cli/subcommands/__init__.py +31 -0
  22. pf_core-0.1.0/src/pf_core/cli/subcommands/_render.py +174 -0
  23. pf_core-0.1.0/src/pf_core/cli/subcommands/baseline.py +155 -0
  24. pf_core-0.1.0/src/pf_core/cli/subcommands/invalidate.py +90 -0
  25. pf_core-0.1.0/src/pf_core/clients/__init__.py +38 -0
  26. pf_core-0.1.0/src/pf_core/clients/anthropic.py +333 -0
  27. pf_core-0.1.0/src/pf_core/clients/brave.py +296 -0
  28. pf_core-0.1.0/src/pf_core/clients/claude_code.py +429 -0
  29. pf_core-0.1.0/src/pf_core/clients/openrouter.py +400 -0
  30. pf_core-0.1.0/src/pf_core/clients/routing.py +167 -0
  31. pf_core-0.1.0/src/pf_core/config.py +149 -0
  32. pf_core-0.1.0/src/pf_core/db/__init__.py +79 -0
  33. pf_core-0.1.0/src/pf_core/db/connection.py +184 -0
  34. pf_core-0.1.0/src/pf_core/db/helpers.py +68 -0
  35. pf_core-0.1.0/src/pf_core/db/json_compat.py +202 -0
  36. pf_core-0.1.0/src/pf_core/db/models.py +79 -0
  37. pf_core-0.1.0/src/pf_core/db/repository.py +66 -0
  38. pf_core-0.1.0/src/pf_core/db/soft_delete.py +122 -0
  39. pf_core-0.1.0/src/pf_core/db/upsert.py +175 -0
  40. pf_core-0.1.0/src/pf_core/db/versioned_config.py +195 -0
  41. pf_core-0.1.0/src/pf_core/docs/INSTALLATION.md +218 -0
  42. pf_core-0.1.0/src/pf_core/docs/alembic.md +71 -0
  43. pf_core-0.1.0/src/pf_core/docs/anthropic.md +172 -0
  44. pf_core-0.1.0/src/pf_core/docs/anti-hallucination.md +123 -0
  45. pf_core-0.1.0/src/pf_core/docs/article-fetch.md +166 -0
  46. pf_core-0.1.0/src/pf_core/docs/brave.md +154 -0
  47. pf_core-0.1.0/src/pf_core/docs/cache.md +178 -0
  48. pf_core-0.1.0/src/pf_core/docs/claude-code.md +211 -0
  49. pf_core-0.1.0/src/pf_core/docs/cli-subcommands.md +112 -0
  50. pf_core-0.1.0/src/pf_core/docs/cli.md +95 -0
  51. pf_core-0.1.0/src/pf_core/docs/config.md +113 -0
  52. pf_core-0.1.0/src/pf_core/docs/cost-budget.md +218 -0
  53. pf_core-0.1.0/src/pf_core/docs/database.md +292 -0
  54. pf_core-0.1.0/src/pf_core/docs/dates.md +130 -0
  55. pf_core-0.1.0/src/pf_core/docs/db-upsert.md +41 -0
  56. pf_core-0.1.0/src/pf_core/docs/env.md +108 -0
  57. pf_core-0.1.0/src/pf_core/docs/eval-harness.md +560 -0
  58. pf_core-0.1.0/src/pf_core/docs/exceptions.md +143 -0
  59. pf_core-0.1.0/src/pf_core/docs/export.md +92 -0
  60. pf_core-0.1.0/src/pf_core/docs/guards.md +57 -0
  61. pf_core-0.1.0/src/pf_core/docs/hashing.md +40 -0
  62. pf_core-0.1.0/src/pf_core/docs/ids.md +95 -0
  63. pf_core-0.1.0/src/pf_core/docs/io.md +83 -0
  64. pf_core-0.1.0/src/pf_core/docs/jobs.md +381 -0
  65. pf_core-0.1.0/src/pf_core/docs/json-recovery.md +94 -0
  66. pf_core-0.1.0/src/pf_core/docs/json-utils.md +112 -0
  67. pf_core-0.1.0/src/pf_core/docs/linting.md +128 -0
  68. pf_core-0.1.0/src/pf_core/docs/llm-admin.md +168 -0
  69. pf_core-0.1.0/src/pf_core/docs/llm-cache.md +276 -0
  70. pf_core-0.1.0/src/pf_core/docs/llm-parse.md +96 -0
  71. pf_core-0.1.0/src/pf_core/docs/llm-safe-apply.md +107 -0
  72. pf_core-0.1.0/src/pf_core/docs/llm-schema-validation.md +445 -0
  73. pf_core-0.1.0/src/pf_core/docs/llm-tracked.md +98 -0
  74. pf_core-0.1.0/src/pf_core/docs/llm-tracking.md +405 -0
  75. pf_core-0.1.0/src/pf_core/docs/llm-validation.md +94 -0
  76. pf_core-0.1.0/src/pf_core/docs/logging.md +116 -0
  77. pf_core-0.1.0/src/pf_core/docs/markdown.md +111 -0
  78. pf_core-0.1.0/src/pf_core/docs/model-router.md +446 -0
  79. pf_core-0.1.0/src/pf_core/docs/modules.md +176 -0
  80. pf_core-0.1.0/src/pf_core/docs/openrouter.md +230 -0
  81. pf_core-0.1.0/src/pf_core/docs/orchestrators.md +93 -0
  82. pf_core-0.1.0/src/pf_core/docs/output.md +123 -0
  83. pf_core-0.1.0/src/pf_core/docs/pagination.md +122 -0
  84. pf_core-0.1.0/src/pf_core/docs/parallel.md +121 -0
  85. pf_core-0.1.0/src/pf_core/docs/parsers.md +99 -0
  86. pf_core-0.1.0/src/pf_core/docs/periods.md +84 -0
  87. pf_core-0.1.0/src/pf_core/docs/phash.md +69 -0
  88. pf_core-0.1.0/src/pf_core/docs/pipeline.md +287 -0
  89. pf_core-0.1.0/src/pf_core/docs/pricing.md +43 -0
  90. pf_core-0.1.0/src/pf_core/docs/project-portability.md +125 -0
  91. pf_core-0.1.0/src/pf_core/docs/prompts.md +277 -0
  92. pf_core-0.1.0/src/pf_core/docs/relative-dates.md +130 -0
  93. pf_core-0.1.0/src/pf_core/docs/scaffold.md +33 -0
  94. pf_core-0.1.0/src/pf_core/docs/services.md +84 -0
  95. pf_core-0.1.0/src/pf_core/docs/similarity.md +102 -0
  96. pf_core-0.1.0/src/pf_core/docs/soft-delete.md +105 -0
  97. pf_core-0.1.0/src/pf_core/docs/test-migration.md +105 -0
  98. pf_core-0.1.0/src/pf_core/docs/testing.md +143 -0
  99. pf_core-0.1.0/src/pf_core/docs/throttle.md +33 -0
  100. pf_core-0.1.0/src/pf_core/docs/urls.md +301 -0
  101. pf_core-0.1.0/src/pf_core/docs/versioned-config.md +99 -0
  102. pf_core-0.1.0/src/pf_core/docs/vocab.md +141 -0
  103. pf_core-0.1.0/src/pf_core/docs/web.md +269 -0
  104. pf_core-0.1.0/src/pf_core/eval/__init__.py +104 -0
  105. pf_core-0.1.0/src/pf_core/eval/_compare.py +146 -0
  106. pf_core-0.1.0/src/pf_core/eval/_config.py +142 -0
  107. pf_core-0.1.0/src/pf_core/eval/_golden.py +205 -0
  108. pf_core-0.1.0/src/pf_core/eval/_judge.py +146 -0
  109. pf_core-0.1.0/src/pf_core/eval/_report.py +214 -0
  110. pf_core-0.1.0/src/pf_core/eval/_runner.py +393 -0
  111. pf_core-0.1.0/src/pf_core/exceptions.py +192 -0
  112. pf_core-0.1.0/src/pf_core/export/__init__.py +16 -0
  113. pf_core-0.1.0/src/pf_core/export/markdown.py +264 -0
  114. pf_core-0.1.0/src/pf_core/guards/__init__.py +18 -0
  115. pf_core-0.1.0/src/pf_core/guards/__main__.py +9 -0
  116. pf_core-0.1.0/src/pf_core/guards/structure.py +174 -0
  117. pf_core-0.1.0/src/pf_core/jobs/__init__.py +69 -0
  118. pf_core-0.1.0/src/pf_core/jobs/_schema.py +156 -0
  119. pf_core-0.1.0/src/pf_core/jobs/registry.py +254 -0
  120. pf_core-0.1.0/src/pf_core/jobs/repo.py +775 -0
  121. pf_core-0.1.0/src/pf_core/jobs/runtime.py +309 -0
  122. pf_core-0.1.0/src/pf_core/llm/__init__.py +107 -0
  123. pf_core-0.1.0/src/pf_core/llm/_router_config.py +161 -0
  124. pf_core-0.1.0/src/pf_core/llm/_router_loader.py +118 -0
  125. pf_core-0.1.0/src/pf_core/llm/_router_schema.py +142 -0
  126. pf_core-0.1.0/src/pf_core/llm/cache/__init__.py +234 -0
  127. pf_core-0.1.0/src/pf_core/llm/cache/_recorder.py +121 -0
  128. pf_core-0.1.0/src/pf_core/llm/cache/_schema.py +111 -0
  129. pf_core-0.1.0/src/pf_core/llm/cache/config.py +137 -0
  130. pf_core-0.1.0/src/pf_core/llm/cache/exact.py +158 -0
  131. pf_core-0.1.0/src/pf_core/llm/cache/invalidate.py +123 -0
  132. pf_core-0.1.0/src/pf_core/llm/parse.py +152 -0
  133. pf_core-0.1.0/src/pf_core/llm/prompts.py +237 -0
  134. pf_core-0.1.0/src/pf_core/llm/router.py +331 -0
  135. pf_core-0.1.0/src/pf_core/llm/safe_apply.py +193 -0
  136. pf_core-0.1.0/src/pf_core/llm/tracked.py +286 -0
  137. pf_core-0.1.0/src/pf_core/llm/tracking/__init__.py +114 -0
  138. pf_core-0.1.0/src/pf_core/llm/tracking/_resolvers.py +275 -0
  139. pf_core-0.1.0/src/pf_core/llm/tracking/decorator.py +198 -0
  140. pf_core-0.1.0/src/pf_core/llm/tracking/purge.py +63 -0
  141. pf_core-0.1.0/src/pf_core/llm/tracking/repo.py +332 -0
  142. pf_core-0.1.0/src/pf_core/llm/tracking/schema.py +482 -0
  143. pf_core-0.1.0/src/pf_core/llm/tracking/stats.py +207 -0
  144. pf_core-0.1.0/src/pf_core/llm/tracking/subrepos.py +230 -0
  145. pf_core-0.1.0/src/pf_core/llm/url_check.py +74 -0
  146. pf_core-0.1.0/src/pf_core/llm/validate/__init__.py +80 -0
  147. pf_core-0.1.0/src/pf_core/llm/validate/_cross_field.py +57 -0
  148. pf_core-0.1.0/src/pf_core/llm/validate/_jsonschema.py +61 -0
  149. pf_core-0.1.0/src/pf_core/llm/validate/_pipeline.py +263 -0
  150. pf_core-0.1.0/src/pf_core/llm/validate/_pydantic.py +51 -0
  151. pf_core-0.1.0/src/pf_core/llm/validate/_registry.py +135 -0
  152. pf_core-0.1.0/src/pf_core/llm/validate/_semantic.py +353 -0
  153. pf_core-0.1.0/src/pf_core/log.py +239 -0
  154. pf_core-0.1.0/src/pf_core/orchestrators/__init__.py +5 -0
  155. pf_core-0.1.0/src/pf_core/orchestrators/base.py +90 -0
  156. pf_core-0.1.0/src/pf_core/output.py +108 -0
  157. pf_core-0.1.0/src/pf_core/parallel.py +249 -0
  158. pf_core-0.1.0/src/pf_core/parsers/__init__.py +48 -0
  159. pf_core-0.1.0/src/pf_core/parsers/exceptions.py +36 -0
  160. pf_core-0.1.0/src/pf_core/parsers/html.py +193 -0
  161. pf_core-0.1.0/src/pf_core/parsers/types.py +32 -0
  162. pf_core-0.1.0/src/pf_core/pipeline/__init__.py +15 -0
  163. pf_core-0.1.0/src/pf_core/pipeline/baseline.py +245 -0
  164. pf_core-0.1.0/src/pf_core/pipeline/baseline_diff.py +297 -0
  165. pf_core-0.1.0/src/pf_core/pipeline/cache.py +159 -0
  166. pf_core-0.1.0/src/pf_core/pipeline/resume.py +130 -0
  167. pf_core-0.1.0/src/pf_core/pipeline/run_record.py +127 -0
  168. pf_core-0.1.0/src/pf_core/pipeline/sequencer.py +161 -0
  169. pf_core-0.1.0/src/pf_core/pricing/__init__.py +30 -0
  170. pf_core-0.1.0/src/pf_core/pricing/_data.py +35 -0
  171. pf_core-0.1.0/src/pf_core/pricing/_resolver.py +112 -0
  172. pf_core-0.1.0/src/pf_core/pricing/_types.py +24 -0
  173. pf_core-0.1.0/src/pf_core/py.typed +0 -0
  174. pf_core-0.1.0/src/pf_core/services/__init__.py +5 -0
  175. pf_core-0.1.0/src/pf_core/services/base.py +70 -0
  176. pf_core-0.1.0/src/pf_core/testing/__init__.py +20 -0
  177. pf_core-0.1.0/src/pf_core/testing/db_fixtures.py +197 -0
  178. pf_core-0.1.0/src/pf_core/testing/fixtures.py +49 -0
  179. pf_core-0.1.0/src/pf_core/utils/__init__.py +58 -0
  180. pf_core-0.1.0/src/pf_core/utils/article_fetch.py +527 -0
  181. pf_core-0.1.0/src/pf_core/utils/dates.py +186 -0
  182. pf_core-0.1.0/src/pf_core/utils/env.py +157 -0
  183. pf_core-0.1.0/src/pf_core/utils/hashing.py +52 -0
  184. pf_core-0.1.0/src/pf_core/utils/ids.py +112 -0
  185. pf_core-0.1.0/src/pf_core/utils/io.py +117 -0
  186. pf_core-0.1.0/src/pf_core/utils/json.py +123 -0
  187. pf_core-0.1.0/src/pf_core/utils/json_recovery.py +188 -0
  188. pf_core-0.1.0/src/pf_core/utils/periods.py +197 -0
  189. pf_core-0.1.0/src/pf_core/utils/phash.py +162 -0
  190. pf_core-0.1.0/src/pf_core/utils/relative_dates.py +247 -0
  191. pf_core-0.1.0/src/pf_core/utils/similarity.py +71 -0
  192. pf_core-0.1.0/src/pf_core/utils/throttle.py +67 -0
  193. pf_core-0.1.0/src/pf_core/utils/url_liveness.py +191 -0
  194. pf_core-0.1.0/src/pf_core/utils/urls.py +518 -0
  195. pf_core-0.1.0/src/pf_core/utils/vocab.py +135 -0
  196. pf_core-0.1.0/src/pf_core/web/__init__.py +14 -0
  197. pf_core-0.1.0/src/pf_core/web/app_factory.py +391 -0
  198. pf_core-0.1.0/src/pf_core/web/health.py +112 -0
  199. pf_core-0.1.0/src/pf_core/web/helpers.py +30 -0
  200. pf_core-0.1.0/src/pf_core/web/json.py +55 -0
  201. pf_core-0.1.0/src/pf_core/web/llm_admin/__init__.py +83 -0
  202. pf_core-0.1.0/src/pf_core/web/llm_admin/api.py +161 -0
  203. pf_core-0.1.0/src/pf_core/web/llm_admin/pages.py +174 -0
  204. pf_core-0.1.0/src/pf_core/web/llm_admin/queries.py +526 -0
  205. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/base.html +60 -0
  206. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/budgets.html +46 -0
  207. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/cache.html +44 -0
  208. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/cost_by_agent.html +22 -0
  209. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/cost_by_model.html +24 -0
  210. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/dashboard.html +41 -0
  211. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/job_detail.html +60 -0
  212. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/jobs_list.html +47 -0
  213. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/macros.html +23 -0
  214. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/run_detail.html +114 -0
  215. pf_core-0.1.0/src/pf_core/web/llm_admin/templates/runs_list.html +49 -0
  216. pf_core-0.1.0/src/pf_core/web/markdown.py +208 -0
  217. pf_core-0.1.0/src/pf_core/web/pagination.py +124 -0
  218. pf_core-0.1.0/src/pf_core/web/rate_limit.py +113 -0
  219. pf_core-0.1.0/src/pf_core/web/templates.py +59 -0
  220. pf_core-0.1.0/src/pf_core.egg-info/PKG-INFO +152 -0
  221. pf_core-0.1.0/src/pf_core.egg-info/SOURCES.txt +313 -0
  222. pf_core-0.1.0/src/pf_core.egg-info/dependency_links.txt +1 -0
  223. pf_core-0.1.0/src/pf_core.egg-info/entry_points.txt +5 -0
  224. pf_core-0.1.0/src/pf_core.egg-info/requires.txt +97 -0
  225. pf_core-0.1.0/src/pf_core.egg-info/top_level.txt +1 -0
  226. pf_core-0.1.0/tests/test_alembic.py +128 -0
  227. pf_core-0.1.0/tests/test_app_factory.py +125 -0
  228. pf_core-0.1.0/tests/test_article_fetch.py +323 -0
  229. pf_core-0.1.0/tests/test_budget.py +529 -0
  230. pf_core-0.1.0/tests/test_budget_scheduler.py +98 -0
  231. pf_core-0.1.0/tests/test_cache.py +123 -0
  232. pf_core-0.1.0/tests/test_cli.py +134 -0
  233. pf_core-0.1.0/tests/test_cli_jobs.py +179 -0
  234. pf_core-0.1.0/tests/test_cli_subcommands.py +522 -0
  235. pf_core-0.1.0/tests/test_clients_anthropic.py +481 -0
  236. pf_core-0.1.0/tests/test_clients_brave.py +245 -0
  237. pf_core-0.1.0/tests/test_clients_claude_code.py +902 -0
  238. pf_core-0.1.0/tests/test_clients_registry.py +191 -0
  239. pf_core-0.1.0/tests/test_clients_routing.py +73 -0
  240. pf_core-0.1.0/tests/test_config.py +186 -0
  241. pf_core-0.1.0/tests/test_dates.py +205 -0
  242. pf_core-0.1.0/tests/test_db_connection.py +158 -0
  243. pf_core-0.1.0/tests/test_db_helpers.py +52 -0
  244. pf_core-0.1.0/tests/test_db_json_compat.py +171 -0
  245. pf_core-0.1.0/tests/test_db_models.py +135 -0
  246. pf_core-0.1.0/tests/test_db_upsert.py +212 -0
  247. pf_core-0.1.0/tests/test_db_versioned_config.py +160 -0
  248. pf_core-0.1.0/tests/test_eval_compare.py +148 -0
  249. pf_core-0.1.0/tests/test_eval_config.py +175 -0
  250. pf_core-0.1.0/tests/test_eval_golden.py +325 -0
  251. pf_core-0.1.0/tests/test_eval_judge.py +58 -0
  252. pf_core-0.1.0/tests/test_eval_runner.py +221 -0
  253. pf_core-0.1.0/tests/test_exceptions.py +101 -0
  254. pf_core-0.1.0/tests/test_export.py +173 -0
  255. pf_core-0.1.0/tests/test_extras_guard.py +24 -0
  256. pf_core-0.1.0/tests/test_guards_cli.py +34 -0
  257. pf_core-0.1.0/tests/test_guards_layering.py +33 -0
  258. pf_core-0.1.0/tests/test_guards_structure.py +58 -0
  259. pf_core-0.1.0/tests/test_health.py +113 -0
  260. pf_core-0.1.0/tests/test_ids.py +111 -0
  261. pf_core-0.1.0/tests/test_json_helpers.py +168 -0
  262. pf_core-0.1.0/tests/test_json_utils.py +89 -0
  263. pf_core-0.1.0/tests/test_llm_admin.py +474 -0
  264. pf_core-0.1.0/tests/test_llm_cache.py +434 -0
  265. pf_core-0.1.0/tests/test_llm_parse.py +121 -0
  266. pf_core-0.1.0/tests/test_llm_router.py +344 -0
  267. pf_core-0.1.0/tests/test_llm_router_nested.py +292 -0
  268. pf_core-0.1.0/tests/test_llm_router_resolve.py +420 -0
  269. pf_core-0.1.0/tests/test_llm_safe_apply.py +225 -0
  270. pf_core-0.1.0/tests/test_llm_tracked.py +240 -0
  271. pf_core-0.1.0/tests/test_llm_tracking_autogenerate.py +115 -0
  272. pf_core-0.1.0/tests/test_llm_tracking_decorator.py +334 -0
  273. pf_core-0.1.0/tests/test_llm_tracking_repos.py +915 -0
  274. pf_core-0.1.0/tests/test_llm_tracking_resolvers.py +247 -0
  275. pf_core-0.1.0/tests/test_llm_tracking_schema.py +438 -0
  276. pf_core-0.1.0/tests/test_llm_validation.py +104 -0
  277. pf_core-0.1.0/tests/test_log.py +221 -0
  278. pf_core-0.1.0/tests/test_markdown.py +158 -0
  279. pf_core-0.1.0/tests/test_new_consumer.py +131 -0
  280. pf_core-0.1.0/tests/test_openrouter.py +588 -0
  281. pf_core-0.1.0/tests/test_orchestrator_base.py +110 -0
  282. pf_core-0.1.0/tests/test_output.py +137 -0
  283. pf_core-0.1.0/tests/test_pagination.py +137 -0
  284. pf_core-0.1.0/tests/test_parallel.py +381 -0
  285. pf_core-0.1.0/tests/test_parsers_exceptions.py +75 -0
  286. pf_core-0.1.0/tests/test_parsers_html.py +259 -0
  287. pf_core-0.1.0/tests/test_pipeline_baseline.py +306 -0
  288. pf_core-0.1.0/tests/test_pipeline_baseline_diff.py +236 -0
  289. pf_core-0.1.0/tests/test_pipeline_cache.py +292 -0
  290. pf_core-0.1.0/tests/test_pipeline_resume.py +220 -0
  291. pf_core-0.1.0/tests/test_pipeline_run_record.py +203 -0
  292. pf_core-0.1.0/tests/test_pipeline_sequencer.py +171 -0
  293. pf_core-0.1.0/tests/test_pricing.py +137 -0
  294. pf_core-0.1.0/tests/test_prompts.py +298 -0
  295. pf_core-0.1.0/tests/test_pyproject_tiers.py +108 -0
  296. pf_core-0.1.0/tests/test_rate_limit.py +107 -0
  297. pf_core-0.1.0/tests/test_relative_dates.py +229 -0
  298. pf_core-0.1.0/tests/test_repository.py +84 -0
  299. pf_core-0.1.0/tests/test_service_base.py +102 -0
  300. pf_core-0.1.0/tests/test_similarity.py +106 -0
  301. pf_core-0.1.0/tests/test_soft_delete.py +162 -0
  302. pf_core-0.1.0/tests/test_templates.py +57 -0
  303. pf_core-0.1.0/tests/test_testing_plugin.py +207 -0
  304. pf_core-0.1.0/tests/test_url_liveness.py +263 -0
  305. pf_core-0.1.0/tests/test_urls.py +808 -0
  306. pf_core-0.1.0/tests/test_utils_env.py +149 -0
  307. pf_core-0.1.0/tests/test_utils_hashing.py +39 -0
  308. pf_core-0.1.0/tests/test_utils_io.py +170 -0
  309. pf_core-0.1.0/tests/test_utils_periods.py +232 -0
  310. pf_core-0.1.0/tests/test_utils_phash.py +167 -0
  311. pf_core-0.1.0/tests/test_utils_throttle.py +72 -0
  312. pf_core-0.1.0/tests/test_utils_vocab.py +181 -0
  313. pf_core-0.1.0/tests/test_version.py +23 -0
  314. pf_core-0.1.0/tests/test_web_helpers.py +59 -0
  315. pf_core-0.1.0/tests/test_web_json.py +75 -0
pf_core-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mike Farr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pf_core-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: pf-core
3
+ Version: 0.1.0
4
+ Summary: Dependency-light Python foundation (logging, exceptions, config, utils) with LLM clients, anti-slop guards, cost tracking, and an opinionated FastAPI+SQLAlchemy framework available as opt-in extras
5
+ Author: Mike Farr
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/phierceweb/pf-core
8
+ Project-URL: Changelog, https://github.com/phierceweb/pf-core/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/phierceweb/pf-core/issues
10
+ Keywords: llm,openrouter,anthropic,fastapi,sqlalchemy,framework,cost-tracking,eval,structured-logging
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: python-dotenv>=1.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: structlog>=24.0
24
+ Requires-Dist: nanoid>=2.0
25
+ Requires-Dist: rich>=13.7
26
+ Provides-Extra: http
27
+ Requires-Dist: httpx>=0.27; extra == "http"
28
+ Provides-Extra: cli
29
+ Requires-Dist: typer>=0.12; extra == "cli"
30
+ Requires-Dist: click>=8.0; extra == "cli"
31
+ Provides-Extra: validate
32
+ Requires-Dist: json-repair>=0.40; extra == "validate"
33
+ Requires-Dist: pydantic>=2.0; extra == "validate"
34
+ Provides-Extra: llm
35
+ Requires-Dist: pf-core[http,validate]; extra == "llm"
36
+ Requires-Dist: tenacity>=8.0; extra == "llm"
37
+ Provides-Extra: db
38
+ Requires-Dist: sqlalchemy>=2.0; extra == "db"
39
+ Requires-Dist: alembic>=1.13; extra == "db"
40
+ Provides-Extra: web
41
+ Requires-Dist: fastapi>=0.115; extra == "web"
42
+ Requires-Dist: jinja2>=3.1; extra == "web"
43
+ Requires-Dist: uvicorn[standard]>=0.30; extra == "web"
44
+ Provides-Extra: jobs
45
+ Requires-Dist: pf-core[cli,db]; extra == "jobs"
46
+ Requires-Dist: pydantic>=2.0; extra == "jobs"
47
+ Provides-Extra: tracking
48
+ Requires-Dist: pf-core[db,llm]; extra == "tracking"
49
+ Provides-Extra: admin
50
+ Requires-Dist: pf-core[tracking,web]; extra == "admin"
51
+ Provides-Extra: eval
52
+ Requires-Dist: pf-core[jobs,tracking]; extra == "eval"
53
+ Provides-Extra: mysql
54
+ Requires-Dist: pymysql>=1.1; extra == "mysql"
55
+ Provides-Extra: postgres
56
+ Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
57
+ Provides-Extra: redis
58
+ Requires-Dist: dogpile.cache>=1.3; extra == "redis"
59
+ Requires-Dist: redis>=5.0; extra == "redis"
60
+ Provides-Extra: ratelimit
61
+ Requires-Dist: pf-core[web]; extra == "ratelimit"
62
+ Requires-Dist: slowapi>=0.1.9; extra == "ratelimit"
63
+ Provides-Extra: jsonschema
64
+ Requires-Dist: jsonschema>=4.0; extra == "jsonschema"
65
+ Provides-Extra: articles
66
+ Requires-Dist: trafilatura>=1.6; extra == "articles"
67
+ Requires-Dist: htmldate>=1.7; extra == "articles"
68
+ Requires-Dist: tenacity>=8.0; extra == "articles"
69
+ Provides-Extra: crawl
70
+ Requires-Dist: pf-core[articles,http]; extra == "crawl"
71
+ Provides-Extra: anthropic
72
+ Requires-Dist: anthropic>=0.30; extra == "anthropic"
73
+ Provides-Extra: image-phash
74
+ Requires-Dist: ImageHash>=4.3; extra == "image-phash"
75
+ Requires-Dist: Pillow>=10.0; extra == "image-phash"
76
+ Provides-Extra: full
77
+ Requires-Dist: pf-core[db]; extra == "full"
78
+ Requires-Dist: pf-core[web]; extra == "full"
79
+ Requires-Dist: pf-core[llm]; extra == "full"
80
+ Requires-Dist: pf-core[cli]; extra == "full"
81
+ Requires-Dist: pf-core[jobs]; extra == "full"
82
+ Requires-Dist: pf-core[tracking]; extra == "full"
83
+ Requires-Dist: pf-core[admin]; extra == "full"
84
+ Requires-Dist: pf-core[eval]; extra == "full"
85
+ Requires-Dist: pf-core[redis]; extra == "full"
86
+ Requires-Dist: pf-core[ratelimit]; extra == "full"
87
+ Requires-Dist: pf-core[jsonschema]; extra == "full"
88
+ Provides-Extra: dev
89
+ Requires-Dist: pytest>=8.0; extra == "dev"
90
+ Requires-Dist: httpx>=0.27; extra == "dev"
91
+ Requires-Dist: factory-boy>=3.3; extra == "dev"
92
+ Requires-Dist: ruff>=0.6; extra == "dev"
93
+ Requires-Dist: pre-commit>=3.5; extra == "dev"
94
+ Provides-Extra: test-containers
95
+ Requires-Dist: testcontainers[mysql]>=4.0; extra == "test-containers"
96
+ Dynamic: license-file
97
+
98
+ # pf-core
99
+
100
+ A dependency-light Python foundation for building LLM applications — and one built to be worked on by AI coding agents as much as by people. The base install provides structured logging, an exception hierarchy, config-from-env, and a service/repo architecture; opt-in extras add LLM clients, output validation, cost tracking and budgets, an eval harness, and a FastAPI + SQLAlchemy app framework. Capabilities compose orthogonally — the foundation alone, the LLM layer without a database, or the web layer without LLMs.
101
+
102
+ ## Built for AI-assisted development
103
+
104
+ The conventions that keep a codebase legible to an AI agent are enforced, not suggested. A build gate fails CI when a file grows past its line budget — small files stay within a model's working context and edit cleanly — and a companion checker flags imports that cross the layered architecture the wrong way. Logging, errors, config, and data access each have one obvious way to do them, documented one-module-per-file for retrieval, so generated code lands in the same shapes as hand-written code instead of drifting. The result is a substrate where an agent can do real work and the guardrails hold.
105
+
106
+ ## One interface over every LLM backend — including Claude Code
107
+
108
+ OpenRouter (paid API), the Anthropic SDK, and Claude Code (a local Claude Max session, $0 per call) sit behind the same `chat(messages, model) -> (content, usage)` interface. A YAML model router assigns a backend per agent and falls back to the next available one; a registry accepts custom backends (Ollama, direct OpenAI, …). Because the clients are interchangeable and `pf_core.parallel` fans work across a thread pool, a batch of LLM calls can run concurrently and route anywhere — a large batch pushed onto a Claude Max subscription instead of spending API credits, or spread across providers — while every call is still tracked and budget-checked the same way.
109
+
110
+ ## Output guards and observability
111
+
112
+ LLMs return fenced, truncated, or not-quite-JSON output; pf-core recovers it (`pf_core.llm.parse`) and validates the result against a schema with optional semantic and cross-field checks (`pf_core.llm.validate`) — available without the client stack, so output from any transport can be guarded. Every call can record one database row (prompt, tokens, cost, validations, and the job it belongs to), making spend and quality queryable and runs replayable. Pre-call budget checks enforce daily/monthly caps with a kill-switch, a cache skips paying for identical calls, prompts are versioned and linked to the runs they produced, and an eval harness replays golden sets against a new model or prompt to show whether a change is an improvement before it ships.
113
+
114
+ ## The rest of the framework
115
+
116
+ A multi-dialect database layer (SQLite / MySQL / PostgreSQL, identical API) with a shared Alembic runner; a FastAPI app factory with self-contained error pages and content negotiation; a job tracker with a state machine, idempotent step history, and worker leases so multi-step work survives restarts; a mountable admin dashboard for runs, costs, and budgets; and pipeline helpers for run-records, baselines, and stage-cascade cache invalidation. See **[docs/modules.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/modules.md)** for the full index.
117
+
118
+ ## Install
119
+
120
+ ```bash
121
+ pip install pf-core # foundation only — no LLM, no DB, no web
122
+ pip install pf-core[validate] # + output guards (no clients/HTTP)
123
+ pip install pf-core[llm] # + LLM clients (includes [validate])
124
+ pip install pf-core[full,postgres] # the whole app framework
125
+ ```
126
+
127
+ Until the first PyPI release, install from a tagged commit:
128
+
129
+ ```bash
130
+ pip install "pf-core[llm] @ git+https://github.com/phierceweb/pf-core.git@v0.1.0"
131
+ ```
132
+
133
+ Pinning to a **tagged release** is recommended for stability — `main` is the development line and may contain unreleased work between tags. Extras compose orthogonally (`[db]` without LLM, `[web]` without `[db]`, `[llm]` standalone); importing a gated module without its extra raises an `ImportError` naming the extra and the pip command. Full matrix and release/update flow: **[docs/INSTALLATION.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/INSTALLATION.md)**.
134
+
135
+ ## Documentation
136
+
137
+ - **[docs/INSTALLATION.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/INSTALLATION.md)** — extras matrix, install/release/update flows, verification
138
+ - **[docs/modules.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/modules.md)** — one-line-per-module index, grouped by concern
139
+ - **[docs/](https://github.com/phierceweb/pf-core/tree/main/src/pf_core/docs)** — per-module reference with usage and parameter detail
140
+ - **[CHANGELOG.md](https://github.com/phierceweb/pf-core/blob/main/CHANGELOG.md)** — release history
141
+
142
+ ## Development
143
+
144
+ ```bash
145
+ python -m venv .venv && source .venv/bin/activate
146
+ pip install -e ".[full,articles,anthropic,image-phash,dev]" # everything, so the full suite runs
147
+ pre-commit install
148
+ pytest
149
+ python bin/verify-bare-install # confirm the base install stays dependency-light
150
+ ```
151
+
152
+ Pytest fixtures auto-register as a plugin via the `pf_core` entry point — no `conftest.py` import needed in consumers. Contribution guidelines: **[CONTRIBUTING.md](https://github.com/phierceweb/pf-core/blob/main/CONTRIBUTING.md)**.
@@ -0,0 +1,55 @@
1
+ # pf-core
2
+
3
+ A dependency-light Python foundation for building LLM applications — and one built to be worked on by AI coding agents as much as by people. The base install provides structured logging, an exception hierarchy, config-from-env, and a service/repo architecture; opt-in extras add LLM clients, output validation, cost tracking and budgets, an eval harness, and a FastAPI + SQLAlchemy app framework. Capabilities compose orthogonally — the foundation alone, the LLM layer without a database, or the web layer without LLMs.
4
+
5
+ ## Built for AI-assisted development
6
+
7
+ The conventions that keep a codebase legible to an AI agent are enforced, not suggested. A build gate fails CI when a file grows past its line budget — small files stay within a model's working context and edit cleanly — and a companion checker flags imports that cross the layered architecture the wrong way. Logging, errors, config, and data access each have one obvious way to do them, documented one-module-per-file for retrieval, so generated code lands in the same shapes as hand-written code instead of drifting. The result is a substrate where an agent can do real work and the guardrails hold.
8
+
9
+ ## One interface over every LLM backend — including Claude Code
10
+
11
+ OpenRouter (paid API), the Anthropic SDK, and Claude Code (a local Claude Max session, $0 per call) sit behind the same `chat(messages, model) -> (content, usage)` interface. A YAML model router assigns a backend per agent and falls back to the next available one; a registry accepts custom backends (Ollama, direct OpenAI, …). Because the clients are interchangeable and `pf_core.parallel` fans work across a thread pool, a batch of LLM calls can run concurrently and route anywhere — a large batch pushed onto a Claude Max subscription instead of spending API credits, or spread across providers — while every call is still tracked and budget-checked the same way.
12
+
13
+ ## Output guards and observability
14
+
15
+ LLMs return fenced, truncated, or not-quite-JSON output; pf-core recovers it (`pf_core.llm.parse`) and validates the result against a schema with optional semantic and cross-field checks (`pf_core.llm.validate`) — available without the client stack, so output from any transport can be guarded. Every call can record one database row (prompt, tokens, cost, validations, and the job it belongs to), making spend and quality queryable and runs replayable. Pre-call budget checks enforce daily/monthly caps with a kill-switch, a cache skips paying for identical calls, prompts are versioned and linked to the runs they produced, and an eval harness replays golden sets against a new model or prompt to show whether a change is an improvement before it ships.
16
+
17
+ ## The rest of the framework
18
+
19
+ A multi-dialect database layer (SQLite / MySQL / PostgreSQL, identical API) with a shared Alembic runner; a FastAPI app factory with self-contained error pages and content negotiation; a job tracker with a state machine, idempotent step history, and worker leases so multi-step work survives restarts; a mountable admin dashboard for runs, costs, and budgets; and pipeline helpers for run-records, baselines, and stage-cascade cache invalidation. See **[docs/modules.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/modules.md)** for the full index.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install pf-core # foundation only — no LLM, no DB, no web
25
+ pip install pf-core[validate] # + output guards (no clients/HTTP)
26
+ pip install pf-core[llm] # + LLM clients (includes [validate])
27
+ pip install pf-core[full,postgres] # the whole app framework
28
+ ```
29
+
30
+ Until the first PyPI release, install from a tagged commit:
31
+
32
+ ```bash
33
+ pip install "pf-core[llm] @ git+https://github.com/phierceweb/pf-core.git@v0.1.0"
34
+ ```
35
+
36
+ Pinning to a **tagged release** is recommended for stability — `main` is the development line and may contain unreleased work between tags. Extras compose orthogonally (`[db]` without LLM, `[web]` without `[db]`, `[llm]` standalone); importing a gated module without its extra raises an `ImportError` naming the extra and the pip command. Full matrix and release/update flow: **[docs/INSTALLATION.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/INSTALLATION.md)**.
37
+
38
+ ## Documentation
39
+
40
+ - **[docs/INSTALLATION.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/INSTALLATION.md)** — extras matrix, install/release/update flows, verification
41
+ - **[docs/modules.md](https://github.com/phierceweb/pf-core/blob/main/src/pf_core/docs/modules.md)** — one-line-per-module index, grouped by concern
42
+ - **[docs/](https://github.com/phierceweb/pf-core/tree/main/src/pf_core/docs)** — per-module reference with usage and parameter detail
43
+ - **[CHANGELOG.md](https://github.com/phierceweb/pf-core/blob/main/CHANGELOG.md)** — release history
44
+
45
+ ## Development
46
+
47
+ ```bash
48
+ python -m venv .venv && source .venv/bin/activate
49
+ pip install -e ".[full,articles,anthropic,image-phash,dev]" # everything, so the full suite runs
50
+ pre-commit install
51
+ pytest
52
+ python bin/verify-bare-install # confirm the base install stays dependency-light
53
+ ```
54
+
55
+ Pytest fixtures auto-register as a plugin via the `pf_core` entry point — no `conftest.py` import needed in consumers. Contribution guidelines: **[CONTRIBUTING.md](https://github.com/phierceweb/pf-core/blob/main/CONTRIBUTING.md)**.
@@ -0,0 +1,273 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pf-core"
7
+ version = "0.1.0"
8
+ description = "Dependency-light Python foundation (logging, exceptions, config, utils) with LLM clients, anti-slop guards, cost tracking, and an opinionated FastAPI+SQLAlchemy framework available as opt-in extras"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Mike Farr" }]
14
+ keywords = [
15
+ "llm",
16
+ "openrouter",
17
+ "anthropic",
18
+ "fastapi",
19
+ "sqlalchemy",
20
+ "framework",
21
+ "cost-tracking",
22
+ "eval",
23
+ "structured-logging",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 4 - Beta",
27
+ "Intended Audience :: Developers",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Typing :: Typed",
33
+ ]
34
+ dependencies = [
35
+ # Foundation kernel: the dependency-light architectural base — structured
36
+ # logging, exception hierarchy, config + env resolvers, utils, the
37
+ # ``Service`` base class, and console output. Everything else (LLM
38
+ # clients + anti-slop guards, HTTP utils, CLI scaffolding, database, web,
39
+ # jobs, eval) lives behind opt-in extras so a project can adopt pf-core's
40
+ # discipline without installing the LLM/HTTP stack. See
41
+ # docs/INSTALLATION.md for the extras matrix.
42
+ #
43
+ # These five are the only hard deps: dotenv (config), pyyaml (yaml config,
44
+ # imported lazily), structlog (logging), nanoid (id generation), rich
45
+ # (console output in pf_core.output). No httpx, pydantic, json-repair,
46
+ # tenacity, or typer here — those moved to [http]/[validate]/[llm]/[cli].
47
+ "python-dotenv>=1.0",
48
+ "pyyaml>=6.0",
49
+ "structlog>=24.0",
50
+ "nanoid>=2.0",
51
+ "rich>=13.7",
52
+ ]
53
+
54
+ [project.optional-dependencies]
55
+ # ---------------------------------------------------------------------------
56
+ # Capability extras — each unlocks a tier of pf-core's framework surface.
57
+ # Each composes orthogonally on the foundation base: [db] without LLM, [web]
58
+ # without [db], [llm] standalone.
59
+ # ---------------------------------------------------------------------------
60
+
61
+ # HTTP utils: pf_core.utils.url_liveness / pf_core.utils.urls (URL liveness +
62
+ # Wayback round-timestamp checks). The lightest network tier — just httpx.
63
+ # Pulled in transitively by [llm].
64
+ http = ["httpx>=0.27"]
65
+
66
+ # CLI scaffolding: pf_core.cli.create_cli. Install this to reuse pf-core's
67
+ # Typer-based command scaffolding in your own CLI; foundation-only projects
68
+ # can equally just depend on typer directly. Pulled in by [jobs].
69
+ cli = [
70
+ "typer>=0.12",
71
+ "click>=8.0",
72
+ ]
73
+
74
+ # Anti-slop output guards: pf_core.llm.parse (+ json-repair recovery) and
75
+ # pf_core.llm.validate (pydantic schema/semantic validation). Pure-Python —
76
+ # NO httpx / clients / tenacity / db — so a project can validate LLM output
77
+ # (or wire its own transport) without the client stack. The stdlib/pyyaml
78
+ # members of pf_core.llm (json_helpers, url_check, prompts, router,
79
+ # safe_apply) need no extra at all once pf_core.llm imports lazily.
80
+ validate = [
81
+ "json-repair>=0.40",
82
+ "pydantic>=2.0",
83
+ ]
84
+
85
+ # LLM tier: pf_core.clients.* (OpenRouter / Brave / Claude Code) on top of
86
+ # the anti-slop guards. = [validate] + [http] (clients need httpx) + tenacity
87
+ # (retry). [llm] ⊇ [validate], so every existing [llm]/[full]/[tracking]
88
+ # consumer keeps the same dependency closure (mirrors [tracking] = [db,llm]).
89
+ # Note: tracked_call records to the DB, so it additionally needs [tracking]
90
+ # (= [db,llm]); importing it without [db] raises a friendly 'tracking' error.
91
+ llm = [
92
+ "pf-core[validate,http]",
93
+ "tenacity>=8.0",
94
+ ]
95
+
96
+ # Database layer: pf_core.db.*, pf_core.alembic, fully-functional cost guards
97
+ # (BudgetRepo / CostRateRepo). Without [db], pf_core.budget.check_budget still
98
+ # imports and short-circuits gracefully; pf_core.db.* and pf_core.alembic
99
+ # raise ImportError. SQLite needs no driver extra (stdlib).
100
+ db = [
101
+ "sqlalchemy>=2.0",
102
+ "alembic>=1.13",
103
+ ]
104
+
105
+ # FastAPI web layer: pf_core.web.app_factory, error pages, markdown,
106
+ # pagination, templates.
107
+ web = [
108
+ "fastapi>=0.115",
109
+ "jinja2>=3.1",
110
+ "uvicorn[standard]>=0.30",
111
+ ]
112
+
113
+ # Job tracker: pf_core.jobs.* (state machine, step history, worker leases)
114
+ # and the pf-jobs CLI. Requires [db] for persistence and [cli] for the CLI;
115
+ # pydantic backs job-argument schema validation in pf_core.jobs.registry.
116
+ jobs = ["pf-core[db,cli]", "pydantic>=2.0"]
117
+
118
+ # DB-backed LLM run tracking: pf_core.llm.tracking.* and pf_core.llm.cache.*
119
+ # (one row per call with prompts, tokens, cost, validations, job
120
+ # attribution). Requires [db] for persistence and [llm] for the parse/retry
121
+ # machinery it records around (tracking code itself uses tenacity).
122
+ tracking = ["pf-core[db,llm]"]
123
+
124
+ # Admin dashboard sub-app: pf_core.web.llm_admin (runs, costs, jobs, cache
125
+ # stats, budgets). Requires [web,tracking].
126
+ admin = ["pf-core[web,tracking]"]
127
+
128
+ # Eval harness: pf_core.eval.* (golden-set replay, structured-diff and
129
+ # LLM-judge comparators, pf-eval CLI). Requires [tracking,jobs].
130
+ eval = ["pf-core[tracking,jobs]"]
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # Database driver extras — pick one per consumer (mutually exclusive).
134
+ # ---------------------------------------------------------------------------
135
+ mysql = ["pymysql>=1.1"]
136
+ postgres = ["psycopg[binary]>=3.2"]
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # Other capability extras
140
+ # ---------------------------------------------------------------------------
141
+
142
+ # Redis-backed cache region: pf_core.cache (with graceful no-op fallback
143
+ # when Redis is unreachable).
144
+ redis = ["dogpile.cache>=1.3", "redis>=5.0"]
145
+
146
+ # Per-IP rate limiting on FastAPI routes. Requires [web].
147
+ ratelimit = [
148
+ "pf-core[web]",
149
+ "slowapi>=0.1.9",
150
+ ]
151
+
152
+ # Strict JSON Schema validation in pf_core.llm.validate (stdlib jsonschema
153
+ # rather than the lighter Pydantic path).
154
+ jsonschema = ["jsonschema>=4.0"]
155
+
156
+ # Article fetch + extract: pf_core.utils.article_fetch (title, body,
157
+ # publish-date with Wayback Machine fallback for paywalled URLs). tenacity
158
+ # drives the Wayback retry loop.
159
+ articles = [
160
+ "trafilatura>=1.6",
161
+ "htmldate>=1.7",
162
+ "tenacity>=8.0",
163
+ ]
164
+
165
+ # Intent-named meta-extra: "I want to crawl/fetch web pages." Composes the
166
+ # fetch+extract stack ([articles]) with the URL-utils tier ([http]) —
167
+ # article_fetch imports pf_core.utils.urls, which needs httpx. Exists so a
168
+ # consumer asking "how do I crawl?" gets one named line instead of having to
169
+ # know the composition. Front-end serving is a different intent: [web].
170
+ crawl = ["pf-core[http,articles]"]
171
+
172
+ # Direct Anthropic API client (pf_core.clients.anthropic). Provides a
173
+ # multimodal-capable alternative to the OpenRouter and Claude Code
174
+ # transports for consumers that want the official SDK's vision support
175
+ # and direct usage / cache-token reporting.
176
+ anthropic = ["anthropic>=0.30"]
177
+
178
+ # Perceptual-hash image dedup: pf_core.utils.phash (DCT-based phash via
179
+ # ImageHash + Pillow). Used by document-extraction consumers to detect
180
+ # repeated page decorations (header logos, footer marks) and re-encoded
181
+ # duplicates that sha256 would miss.
182
+ image-phash = [
183
+ "ImageHash>=4.3",
184
+ "Pillow>=10.0",
185
+ ]
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # Meta-extra for full-stack apps that want the whole framework.
189
+ # Add the dialect driver ([mysql]/[postgres]) and any optional capability
190
+ # extras ([articles]) separately.
191
+ # ---------------------------------------------------------------------------
192
+ full = [
193
+ "pf-core[db]",
194
+ "pf-core[web]",
195
+ "pf-core[llm]",
196
+ "pf-core[cli]",
197
+ "pf-core[jobs]",
198
+ "pf-core[tracking]",
199
+ "pf-core[admin]",
200
+ "pf-core[eval]",
201
+ "pf-core[redis]",
202
+ "pf-core[ratelimit]",
203
+ "pf-core[jsonschema]",
204
+ ]
205
+
206
+ # ---------------------------------------------------------------------------
207
+ # Dev / test extras
208
+ # ---------------------------------------------------------------------------
209
+ dev = [
210
+ "pytest>=8.0",
211
+ "httpx>=0.27",
212
+ "factory-boy>=3.3",
213
+ "ruff>=0.6",
214
+ "pre-commit>=3.5",
215
+ ]
216
+ test-containers = [
217
+ "testcontainers[mysql]>=4.0",
218
+ ]
219
+
220
+ [project.urls]
221
+ Repository = "https://github.com/phierceweb/pf-core"
222
+ Changelog = "https://github.com/phierceweb/pf-core/blob/main/CHANGELOG.md"
223
+ Issues = "https://github.com/phierceweb/pf-core/issues"
224
+
225
+ [project.scripts]
226
+ pf-guards = "pf_core.guards.structure:run_cli"
227
+
228
+ [project.entry-points.pytest11]
229
+ pf_core = "pf_core.testing.fixtures"
230
+
231
+ [tool.setuptools.packages.find]
232
+ where = ["src"]
233
+
234
+ [tool.setuptools.package-data]
235
+ "pf_core" = ["docs/*.md", "py.typed"]
236
+ "pf_core.web.llm_admin" = ["templates/*.html"]
237
+
238
+ [tool.pytest.ini_options]
239
+ testpaths = ["tests"] # don't collect templates/consumer-*/tests/ (they hold __PKG__ tokens)
240
+ pythonpath = ["."]
241
+
242
+ # ---------------------------------------------------------------------------
243
+ # Lint config — ruff
244
+ # ---------------------------------------------------------------------------
245
+ # Conservative starting set: what ruff catches by default (E, W, F) plus
246
+ # bugbear (B) for common bug/antipattern checks. isort (I) and pyupgrade
247
+ # (UP) are deferred to a separate formatting pass to keep enforcement
248
+ # diffs reviewable. Line length (E501) is also deferred — most existing
249
+ # code is within 100, but enforcing E501 would force a layout pass we
250
+ # haven't decided on yet.
251
+ [tool.ruff]
252
+ line-length = 100
253
+ target-version = "py311"
254
+
255
+ [tool.ruff.lint]
256
+ select = [
257
+ "E", # pycodestyle errors
258
+ "W", # pycodestyle warnings
259
+ "F", # pyflakes (unused imports, undefined names, etc.)
260
+ "B", # flake8-bugbear (common bugs / antipatterns)
261
+ ]
262
+ ignore = [
263
+ "E501", # line too long — defer to a separate formatting pass
264
+ "B008", # function call in default arg — common in FastAPI / Typer
265
+ "B904", # raise-from — `raise X from y` discipline pre-dates this lint
266
+ ]
267
+
268
+ [tool.ruff.lint.per-file-ignores]
269
+ # Test files are allowed to use bare asserts and unused-but-imported
270
+ # fixtures; F841 (unused local) often shows up when a test imports a
271
+ # class only to verify it's importable. Keep these lints active in
272
+ # `src/` where they actually catch bugs.
273
+ "tests/**/*.py" = ["B011"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """pf-core: a dependency-light Python foundation.
2
+
3
+ The base install (``pip install pf-core``) is the architectural foundation
4
+ only: structured logging, an exception hierarchy, config + env resolvers,
5
+ utils, and the ``Service`` base class — five small deps (structlog,
6
+ python-dotenv, pyyaml, nanoid, rich), no httpx/pydantic/LLM stack.
7
+
8
+ Everything else ships as opt-in, orthogonally-composable extras: anti-slop
9
+ output guards (``[validate]``), LLM clients (``[llm]`` ⊇ ``[validate]``), HTTP
10
+ utils (``[http]``), CLI scaffolding (``[cli]``), and the FastAPI + SQLAlchemy
11
+ app framework (``[db]``, ``[web]``, ``[jobs]``, ``[tracking]``, ``[eval]``,
12
+ ``[admin]``). See ``docs/INSTALLATION.md`` for the extras matrix.
13
+ """
14
+
15
+ from importlib.metadata import PackageNotFoundError, version
16
+
17
+ try:
18
+ __version__ = version("pf-core")
19
+ except PackageNotFoundError: # running from a source tree with no install
20
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,64 @@
1
+ """Friendly errors for missing optional-dependency extras.
2
+
3
+ The foundation install (``pip install pf-core``) is dependency-light: it does
4
+ not ship httpx, pydantic, json-repair, tenacity, or typer. Modules that need
5
+ those live behind opt-in extras ([http], [llm], [cli], [jobs], ...). When such
6
+ a module is imported without its extra installed, the bare third-party
7
+ ``ImportError`` ("No module named 'json_repair'") is opaque. This helper turns
8
+ it into a message that names the extra and the exact pip command.
9
+
10
+ Usage at the top of a gated leaf module::
11
+
12
+ try:
13
+ import httpx
14
+ except ImportError as e: # pragma: no cover - exercised by bare-install CI
15
+ from pf_core._extras import extra_import_error
16
+
17
+ raise extra_import_error(
18
+ "llm", "httpx", feature="pf_core.clients.openrouter"
19
+ ) from e
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ # Extra name -> the pip target a user should install. Anything not listed
25
+ # falls back to ``pf-core[<extra>]``.
26
+ _INSTALL: dict[str, str] = {
27
+ "http": "pf-core[http]",
28
+ "cli": "pf-core[cli]",
29
+ "validate": "pf-core[validate]",
30
+ "llm": "pf-core[llm]",
31
+ "db": "pf-core[db]",
32
+ "web": "pf-core[web]",
33
+ "jobs": "pf-core[jobs]",
34
+ "tracking": "pf-core[tracking]",
35
+ "eval": "pf-core[eval]",
36
+ "admin": "pf-core[admin]",
37
+ "articles": "pf-core[articles]",
38
+ "jsonschema": "pf-core[jsonschema]",
39
+ "redis": "pf-core[redis]",
40
+ "ratelimit": "pf-core[ratelimit]",
41
+ }
42
+
43
+
44
+ def install_target(extra: str) -> str:
45
+ """Return the ``pip install`` target for an extra (e.g. ``pf-core[llm]``)."""
46
+ return _INSTALL.get(extra, f"pf-core[{extra}]")
47
+
48
+
49
+ def extra_import_error(extra: str, package: str, *, feature: str) -> ImportError:
50
+ """Build an ``ImportError`` that names the missing extra and pip command.
51
+
52
+ Args:
53
+ extra: The optional-dependency extra that ships ``package`` (e.g. ``"llm"``).
54
+ package: The third-party import name that failed (e.g. ``"json_repair"``).
55
+ feature: The pf-core module or capability the caller was importing, used
56
+ in the message (e.g. ``"pf_core.llm.parse"``).
57
+
58
+ Returns:
59
+ An ``ImportError`` to ``raise ... from`` the original failure.
60
+ """
61
+ return ImportError(
62
+ f"{feature} requires the '{extra}' extra; '{package}' is not installed. "
63
+ f"Install it with: pip install {install_target(extra)}"
64
+ )
@@ -0,0 +1,72 @@
1
+ """
2
+ Alembic migration helper — shared env.py logic for all projects.
3
+
4
+ Supports SQLite (batch mode), MySQL/MariaDB, and PostgreSQL. Uses pf_core.db
5
+ for engine management so migrations share the same connection config as the app.
6
+
7
+ Usage in a project's ``alembic/env.py``::
8
+
9
+ from pf_core.alembic import run_migrations_online
10
+
11
+ run_migrations_online()
12
+
13
+ With SQLite fallback (e.g., an essay-grading app on SQLite)::
14
+
15
+ from pf_core.alembic import run_migrations_online
16
+
17
+ run_migrations_online(fallback_sqlite="grading.db")
18
+
19
+ With explicit metadata (for autogenerate)::
20
+
21
+ from myapp.models import Base
22
+ from pf_core.alembic import run_migrations_online
23
+
24
+ run_migrations_online(target_metadata=Base.metadata)
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from typing import Any
30
+
31
+ from alembic import context
32
+
33
+ from pf_core.db import db_url, get_engine, is_sqlite
34
+
35
+
36
+ def run_migrations_online(
37
+ *,
38
+ fallback_sqlite: str = "",
39
+ target_metadata: Any = None,
40
+ compare_type: bool = False,
41
+ ) -> None:
42
+ """Run Alembic migrations in online mode.
43
+
44
+ Call this from your project's ``alembic/env.py``. Handles:
45
+ - SQLite batch mode (``render_as_batch=True``)
46
+ - MySQL/MariaDB and PostgreSQL standard mode
47
+ - Engine reuse via ``pf_core.db.get_engine()``
48
+
49
+ Args:
50
+ fallback_sqlite: Path to SQLite file if DATABASE_URL is not set.
51
+ target_metadata: SQLAlchemy MetaData for autogenerate support.
52
+ Pass ``None`` (default) for raw-SQL migrations.
53
+ compare_type: Whether Alembic should detect column type changes.
54
+ """
55
+ if context.is_offline_mode():
56
+ raise RuntimeError(
57
+ "Offline mode is not supported. Set DATABASE_URL and run in online mode."
58
+ )
59
+
60
+ url = db_url(fallback_sqlite=fallback_sqlite)
61
+ sqlite = is_sqlite(url)
62
+ engine = get_engine(url)
63
+
64
+ with engine.connect() as connection:
65
+ context.configure(
66
+ connection=connection,
67
+ target_metadata=target_metadata,
68
+ compare_type=compare_type,
69
+ render_as_batch=sqlite,
70
+ )
71
+ with context.begin_transaction():
72
+ context.run_migrations()