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.
- pf_core-0.1.0/LICENSE +21 -0
- pf_core-0.1.0/PKG-INFO +152 -0
- pf_core-0.1.0/README.md +55 -0
- pf_core-0.1.0/pyproject.toml +273 -0
- pf_core-0.1.0/setup.cfg +4 -0
- pf_core-0.1.0/src/pf_core/__init__.py +20 -0
- pf_core-0.1.0/src/pf_core/_extras.py +64 -0
- pf_core-0.1.0/src/pf_core/alembic.py +72 -0
- pf_core-0.1.0/src/pf_core/budget/__init__.py +147 -0
- pf_core-0.1.0/src/pf_core/budget/_schema.py +122 -0
- pf_core-0.1.0/src/pf_core/budget/audit.py +73 -0
- pf_core-0.1.0/src/pf_core/budget/check.py +369 -0
- pf_core-0.1.0/src/pf_core/budget/config.py +155 -0
- pf_core-0.1.0/src/pf_core/budget/repo.py +386 -0
- pf_core-0.1.0/src/pf_core/budget/scheduler.py +72 -0
- pf_core-0.1.0/src/pf_core/budget/snapshot_job.py +54 -0
- pf_core-0.1.0/src/pf_core/cache/__init__.py +0 -0
- pf_core-0.1.0/src/pf_core/cache/redis.py +198 -0
- pf_core-0.1.0/src/pf_core/cli/__init__.py +103 -0
- pf_core-0.1.0/src/pf_core/cli/jobs.py +272 -0
- pf_core-0.1.0/src/pf_core/cli/subcommands/__init__.py +31 -0
- pf_core-0.1.0/src/pf_core/cli/subcommands/_render.py +174 -0
- pf_core-0.1.0/src/pf_core/cli/subcommands/baseline.py +155 -0
- pf_core-0.1.0/src/pf_core/cli/subcommands/invalidate.py +90 -0
- pf_core-0.1.0/src/pf_core/clients/__init__.py +38 -0
- pf_core-0.1.0/src/pf_core/clients/anthropic.py +333 -0
- pf_core-0.1.0/src/pf_core/clients/brave.py +296 -0
- pf_core-0.1.0/src/pf_core/clients/claude_code.py +429 -0
- pf_core-0.1.0/src/pf_core/clients/openrouter.py +400 -0
- pf_core-0.1.0/src/pf_core/clients/routing.py +167 -0
- pf_core-0.1.0/src/pf_core/config.py +149 -0
- pf_core-0.1.0/src/pf_core/db/__init__.py +79 -0
- pf_core-0.1.0/src/pf_core/db/connection.py +184 -0
- pf_core-0.1.0/src/pf_core/db/helpers.py +68 -0
- pf_core-0.1.0/src/pf_core/db/json_compat.py +202 -0
- pf_core-0.1.0/src/pf_core/db/models.py +79 -0
- pf_core-0.1.0/src/pf_core/db/repository.py +66 -0
- pf_core-0.1.0/src/pf_core/db/soft_delete.py +122 -0
- pf_core-0.1.0/src/pf_core/db/upsert.py +175 -0
- pf_core-0.1.0/src/pf_core/db/versioned_config.py +195 -0
- pf_core-0.1.0/src/pf_core/docs/INSTALLATION.md +218 -0
- pf_core-0.1.0/src/pf_core/docs/alembic.md +71 -0
- pf_core-0.1.0/src/pf_core/docs/anthropic.md +172 -0
- pf_core-0.1.0/src/pf_core/docs/anti-hallucination.md +123 -0
- pf_core-0.1.0/src/pf_core/docs/article-fetch.md +166 -0
- pf_core-0.1.0/src/pf_core/docs/brave.md +154 -0
- pf_core-0.1.0/src/pf_core/docs/cache.md +178 -0
- pf_core-0.1.0/src/pf_core/docs/claude-code.md +211 -0
- pf_core-0.1.0/src/pf_core/docs/cli-subcommands.md +112 -0
- pf_core-0.1.0/src/pf_core/docs/cli.md +95 -0
- pf_core-0.1.0/src/pf_core/docs/config.md +113 -0
- pf_core-0.1.0/src/pf_core/docs/cost-budget.md +218 -0
- pf_core-0.1.0/src/pf_core/docs/database.md +292 -0
- pf_core-0.1.0/src/pf_core/docs/dates.md +130 -0
- pf_core-0.1.0/src/pf_core/docs/db-upsert.md +41 -0
- pf_core-0.1.0/src/pf_core/docs/env.md +108 -0
- pf_core-0.1.0/src/pf_core/docs/eval-harness.md +560 -0
- pf_core-0.1.0/src/pf_core/docs/exceptions.md +143 -0
- pf_core-0.1.0/src/pf_core/docs/export.md +92 -0
- pf_core-0.1.0/src/pf_core/docs/guards.md +57 -0
- pf_core-0.1.0/src/pf_core/docs/hashing.md +40 -0
- pf_core-0.1.0/src/pf_core/docs/ids.md +95 -0
- pf_core-0.1.0/src/pf_core/docs/io.md +83 -0
- pf_core-0.1.0/src/pf_core/docs/jobs.md +381 -0
- pf_core-0.1.0/src/pf_core/docs/json-recovery.md +94 -0
- pf_core-0.1.0/src/pf_core/docs/json-utils.md +112 -0
- pf_core-0.1.0/src/pf_core/docs/linting.md +128 -0
- pf_core-0.1.0/src/pf_core/docs/llm-admin.md +168 -0
- pf_core-0.1.0/src/pf_core/docs/llm-cache.md +276 -0
- pf_core-0.1.0/src/pf_core/docs/llm-parse.md +96 -0
- pf_core-0.1.0/src/pf_core/docs/llm-safe-apply.md +107 -0
- pf_core-0.1.0/src/pf_core/docs/llm-schema-validation.md +445 -0
- pf_core-0.1.0/src/pf_core/docs/llm-tracked.md +98 -0
- pf_core-0.1.0/src/pf_core/docs/llm-tracking.md +405 -0
- pf_core-0.1.0/src/pf_core/docs/llm-validation.md +94 -0
- pf_core-0.1.0/src/pf_core/docs/logging.md +116 -0
- pf_core-0.1.0/src/pf_core/docs/markdown.md +111 -0
- pf_core-0.1.0/src/pf_core/docs/model-router.md +446 -0
- pf_core-0.1.0/src/pf_core/docs/modules.md +176 -0
- pf_core-0.1.0/src/pf_core/docs/openrouter.md +230 -0
- pf_core-0.1.0/src/pf_core/docs/orchestrators.md +93 -0
- pf_core-0.1.0/src/pf_core/docs/output.md +123 -0
- pf_core-0.1.0/src/pf_core/docs/pagination.md +122 -0
- pf_core-0.1.0/src/pf_core/docs/parallel.md +121 -0
- pf_core-0.1.0/src/pf_core/docs/parsers.md +99 -0
- pf_core-0.1.0/src/pf_core/docs/periods.md +84 -0
- pf_core-0.1.0/src/pf_core/docs/phash.md +69 -0
- pf_core-0.1.0/src/pf_core/docs/pipeline.md +287 -0
- pf_core-0.1.0/src/pf_core/docs/pricing.md +43 -0
- pf_core-0.1.0/src/pf_core/docs/project-portability.md +125 -0
- pf_core-0.1.0/src/pf_core/docs/prompts.md +277 -0
- pf_core-0.1.0/src/pf_core/docs/relative-dates.md +130 -0
- pf_core-0.1.0/src/pf_core/docs/scaffold.md +33 -0
- pf_core-0.1.0/src/pf_core/docs/services.md +84 -0
- pf_core-0.1.0/src/pf_core/docs/similarity.md +102 -0
- pf_core-0.1.0/src/pf_core/docs/soft-delete.md +105 -0
- pf_core-0.1.0/src/pf_core/docs/test-migration.md +105 -0
- pf_core-0.1.0/src/pf_core/docs/testing.md +143 -0
- pf_core-0.1.0/src/pf_core/docs/throttle.md +33 -0
- pf_core-0.1.0/src/pf_core/docs/urls.md +301 -0
- pf_core-0.1.0/src/pf_core/docs/versioned-config.md +99 -0
- pf_core-0.1.0/src/pf_core/docs/vocab.md +141 -0
- pf_core-0.1.0/src/pf_core/docs/web.md +269 -0
- pf_core-0.1.0/src/pf_core/eval/__init__.py +104 -0
- pf_core-0.1.0/src/pf_core/eval/_compare.py +146 -0
- pf_core-0.1.0/src/pf_core/eval/_config.py +142 -0
- pf_core-0.1.0/src/pf_core/eval/_golden.py +205 -0
- pf_core-0.1.0/src/pf_core/eval/_judge.py +146 -0
- pf_core-0.1.0/src/pf_core/eval/_report.py +214 -0
- pf_core-0.1.0/src/pf_core/eval/_runner.py +393 -0
- pf_core-0.1.0/src/pf_core/exceptions.py +192 -0
- pf_core-0.1.0/src/pf_core/export/__init__.py +16 -0
- pf_core-0.1.0/src/pf_core/export/markdown.py +264 -0
- pf_core-0.1.0/src/pf_core/guards/__init__.py +18 -0
- pf_core-0.1.0/src/pf_core/guards/__main__.py +9 -0
- pf_core-0.1.0/src/pf_core/guards/structure.py +174 -0
- pf_core-0.1.0/src/pf_core/jobs/__init__.py +69 -0
- pf_core-0.1.0/src/pf_core/jobs/_schema.py +156 -0
- pf_core-0.1.0/src/pf_core/jobs/registry.py +254 -0
- pf_core-0.1.0/src/pf_core/jobs/repo.py +775 -0
- pf_core-0.1.0/src/pf_core/jobs/runtime.py +309 -0
- pf_core-0.1.0/src/pf_core/llm/__init__.py +107 -0
- pf_core-0.1.0/src/pf_core/llm/_router_config.py +161 -0
- pf_core-0.1.0/src/pf_core/llm/_router_loader.py +118 -0
- pf_core-0.1.0/src/pf_core/llm/_router_schema.py +142 -0
- pf_core-0.1.0/src/pf_core/llm/cache/__init__.py +234 -0
- pf_core-0.1.0/src/pf_core/llm/cache/_recorder.py +121 -0
- pf_core-0.1.0/src/pf_core/llm/cache/_schema.py +111 -0
- pf_core-0.1.0/src/pf_core/llm/cache/config.py +137 -0
- pf_core-0.1.0/src/pf_core/llm/cache/exact.py +158 -0
- pf_core-0.1.0/src/pf_core/llm/cache/invalidate.py +123 -0
- pf_core-0.1.0/src/pf_core/llm/parse.py +152 -0
- pf_core-0.1.0/src/pf_core/llm/prompts.py +237 -0
- pf_core-0.1.0/src/pf_core/llm/router.py +331 -0
- pf_core-0.1.0/src/pf_core/llm/safe_apply.py +193 -0
- pf_core-0.1.0/src/pf_core/llm/tracked.py +286 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/__init__.py +114 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/_resolvers.py +275 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/decorator.py +198 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/purge.py +63 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/repo.py +332 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/schema.py +482 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/stats.py +207 -0
- pf_core-0.1.0/src/pf_core/llm/tracking/subrepos.py +230 -0
- pf_core-0.1.0/src/pf_core/llm/url_check.py +74 -0
- pf_core-0.1.0/src/pf_core/llm/validate/__init__.py +80 -0
- pf_core-0.1.0/src/pf_core/llm/validate/_cross_field.py +57 -0
- pf_core-0.1.0/src/pf_core/llm/validate/_jsonschema.py +61 -0
- pf_core-0.1.0/src/pf_core/llm/validate/_pipeline.py +263 -0
- pf_core-0.1.0/src/pf_core/llm/validate/_pydantic.py +51 -0
- pf_core-0.1.0/src/pf_core/llm/validate/_registry.py +135 -0
- pf_core-0.1.0/src/pf_core/llm/validate/_semantic.py +353 -0
- pf_core-0.1.0/src/pf_core/log.py +239 -0
- pf_core-0.1.0/src/pf_core/orchestrators/__init__.py +5 -0
- pf_core-0.1.0/src/pf_core/orchestrators/base.py +90 -0
- pf_core-0.1.0/src/pf_core/output.py +108 -0
- pf_core-0.1.0/src/pf_core/parallel.py +249 -0
- pf_core-0.1.0/src/pf_core/parsers/__init__.py +48 -0
- pf_core-0.1.0/src/pf_core/parsers/exceptions.py +36 -0
- pf_core-0.1.0/src/pf_core/parsers/html.py +193 -0
- pf_core-0.1.0/src/pf_core/parsers/types.py +32 -0
- pf_core-0.1.0/src/pf_core/pipeline/__init__.py +15 -0
- pf_core-0.1.0/src/pf_core/pipeline/baseline.py +245 -0
- pf_core-0.1.0/src/pf_core/pipeline/baseline_diff.py +297 -0
- pf_core-0.1.0/src/pf_core/pipeline/cache.py +159 -0
- pf_core-0.1.0/src/pf_core/pipeline/resume.py +130 -0
- pf_core-0.1.0/src/pf_core/pipeline/run_record.py +127 -0
- pf_core-0.1.0/src/pf_core/pipeline/sequencer.py +161 -0
- pf_core-0.1.0/src/pf_core/pricing/__init__.py +30 -0
- pf_core-0.1.0/src/pf_core/pricing/_data.py +35 -0
- pf_core-0.1.0/src/pf_core/pricing/_resolver.py +112 -0
- pf_core-0.1.0/src/pf_core/pricing/_types.py +24 -0
- pf_core-0.1.0/src/pf_core/py.typed +0 -0
- pf_core-0.1.0/src/pf_core/services/__init__.py +5 -0
- pf_core-0.1.0/src/pf_core/services/base.py +70 -0
- pf_core-0.1.0/src/pf_core/testing/__init__.py +20 -0
- pf_core-0.1.0/src/pf_core/testing/db_fixtures.py +197 -0
- pf_core-0.1.0/src/pf_core/testing/fixtures.py +49 -0
- pf_core-0.1.0/src/pf_core/utils/__init__.py +58 -0
- pf_core-0.1.0/src/pf_core/utils/article_fetch.py +527 -0
- pf_core-0.1.0/src/pf_core/utils/dates.py +186 -0
- pf_core-0.1.0/src/pf_core/utils/env.py +157 -0
- pf_core-0.1.0/src/pf_core/utils/hashing.py +52 -0
- pf_core-0.1.0/src/pf_core/utils/ids.py +112 -0
- pf_core-0.1.0/src/pf_core/utils/io.py +117 -0
- pf_core-0.1.0/src/pf_core/utils/json.py +123 -0
- pf_core-0.1.0/src/pf_core/utils/json_recovery.py +188 -0
- pf_core-0.1.0/src/pf_core/utils/periods.py +197 -0
- pf_core-0.1.0/src/pf_core/utils/phash.py +162 -0
- pf_core-0.1.0/src/pf_core/utils/relative_dates.py +247 -0
- pf_core-0.1.0/src/pf_core/utils/similarity.py +71 -0
- pf_core-0.1.0/src/pf_core/utils/throttle.py +67 -0
- pf_core-0.1.0/src/pf_core/utils/url_liveness.py +191 -0
- pf_core-0.1.0/src/pf_core/utils/urls.py +518 -0
- pf_core-0.1.0/src/pf_core/utils/vocab.py +135 -0
- pf_core-0.1.0/src/pf_core/web/__init__.py +14 -0
- pf_core-0.1.0/src/pf_core/web/app_factory.py +391 -0
- pf_core-0.1.0/src/pf_core/web/health.py +112 -0
- pf_core-0.1.0/src/pf_core/web/helpers.py +30 -0
- pf_core-0.1.0/src/pf_core/web/json.py +55 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/__init__.py +83 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/api.py +161 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/pages.py +174 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/queries.py +526 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/base.html +60 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/budgets.html +46 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/cache.html +44 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/cost_by_agent.html +22 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/cost_by_model.html +24 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/dashboard.html +41 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/job_detail.html +60 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/jobs_list.html +47 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/macros.html +23 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/run_detail.html +114 -0
- pf_core-0.1.0/src/pf_core/web/llm_admin/templates/runs_list.html +49 -0
- pf_core-0.1.0/src/pf_core/web/markdown.py +208 -0
- pf_core-0.1.0/src/pf_core/web/pagination.py +124 -0
- pf_core-0.1.0/src/pf_core/web/rate_limit.py +113 -0
- pf_core-0.1.0/src/pf_core/web/templates.py +59 -0
- pf_core-0.1.0/src/pf_core.egg-info/PKG-INFO +152 -0
- pf_core-0.1.0/src/pf_core.egg-info/SOURCES.txt +313 -0
- pf_core-0.1.0/src/pf_core.egg-info/dependency_links.txt +1 -0
- pf_core-0.1.0/src/pf_core.egg-info/entry_points.txt +5 -0
- pf_core-0.1.0/src/pf_core.egg-info/requires.txt +97 -0
- pf_core-0.1.0/src/pf_core.egg-info/top_level.txt +1 -0
- pf_core-0.1.0/tests/test_alembic.py +128 -0
- pf_core-0.1.0/tests/test_app_factory.py +125 -0
- pf_core-0.1.0/tests/test_article_fetch.py +323 -0
- pf_core-0.1.0/tests/test_budget.py +529 -0
- pf_core-0.1.0/tests/test_budget_scheduler.py +98 -0
- pf_core-0.1.0/tests/test_cache.py +123 -0
- pf_core-0.1.0/tests/test_cli.py +134 -0
- pf_core-0.1.0/tests/test_cli_jobs.py +179 -0
- pf_core-0.1.0/tests/test_cli_subcommands.py +522 -0
- pf_core-0.1.0/tests/test_clients_anthropic.py +481 -0
- pf_core-0.1.0/tests/test_clients_brave.py +245 -0
- pf_core-0.1.0/tests/test_clients_claude_code.py +902 -0
- pf_core-0.1.0/tests/test_clients_registry.py +191 -0
- pf_core-0.1.0/tests/test_clients_routing.py +73 -0
- pf_core-0.1.0/tests/test_config.py +186 -0
- pf_core-0.1.0/tests/test_dates.py +205 -0
- pf_core-0.1.0/tests/test_db_connection.py +158 -0
- pf_core-0.1.0/tests/test_db_helpers.py +52 -0
- pf_core-0.1.0/tests/test_db_json_compat.py +171 -0
- pf_core-0.1.0/tests/test_db_models.py +135 -0
- pf_core-0.1.0/tests/test_db_upsert.py +212 -0
- pf_core-0.1.0/tests/test_db_versioned_config.py +160 -0
- pf_core-0.1.0/tests/test_eval_compare.py +148 -0
- pf_core-0.1.0/tests/test_eval_config.py +175 -0
- pf_core-0.1.0/tests/test_eval_golden.py +325 -0
- pf_core-0.1.0/tests/test_eval_judge.py +58 -0
- pf_core-0.1.0/tests/test_eval_runner.py +221 -0
- pf_core-0.1.0/tests/test_exceptions.py +101 -0
- pf_core-0.1.0/tests/test_export.py +173 -0
- pf_core-0.1.0/tests/test_extras_guard.py +24 -0
- pf_core-0.1.0/tests/test_guards_cli.py +34 -0
- pf_core-0.1.0/tests/test_guards_layering.py +33 -0
- pf_core-0.1.0/tests/test_guards_structure.py +58 -0
- pf_core-0.1.0/tests/test_health.py +113 -0
- pf_core-0.1.0/tests/test_ids.py +111 -0
- pf_core-0.1.0/tests/test_json_helpers.py +168 -0
- pf_core-0.1.0/tests/test_json_utils.py +89 -0
- pf_core-0.1.0/tests/test_llm_admin.py +474 -0
- pf_core-0.1.0/tests/test_llm_cache.py +434 -0
- pf_core-0.1.0/tests/test_llm_parse.py +121 -0
- pf_core-0.1.0/tests/test_llm_router.py +344 -0
- pf_core-0.1.0/tests/test_llm_router_nested.py +292 -0
- pf_core-0.1.0/tests/test_llm_router_resolve.py +420 -0
- pf_core-0.1.0/tests/test_llm_safe_apply.py +225 -0
- pf_core-0.1.0/tests/test_llm_tracked.py +240 -0
- pf_core-0.1.0/tests/test_llm_tracking_autogenerate.py +115 -0
- pf_core-0.1.0/tests/test_llm_tracking_decorator.py +334 -0
- pf_core-0.1.0/tests/test_llm_tracking_repos.py +915 -0
- pf_core-0.1.0/tests/test_llm_tracking_resolvers.py +247 -0
- pf_core-0.1.0/tests/test_llm_tracking_schema.py +438 -0
- pf_core-0.1.0/tests/test_llm_validation.py +104 -0
- pf_core-0.1.0/tests/test_log.py +221 -0
- pf_core-0.1.0/tests/test_markdown.py +158 -0
- pf_core-0.1.0/tests/test_new_consumer.py +131 -0
- pf_core-0.1.0/tests/test_openrouter.py +588 -0
- pf_core-0.1.0/tests/test_orchestrator_base.py +110 -0
- pf_core-0.1.0/tests/test_output.py +137 -0
- pf_core-0.1.0/tests/test_pagination.py +137 -0
- pf_core-0.1.0/tests/test_parallel.py +381 -0
- pf_core-0.1.0/tests/test_parsers_exceptions.py +75 -0
- pf_core-0.1.0/tests/test_parsers_html.py +259 -0
- pf_core-0.1.0/tests/test_pipeline_baseline.py +306 -0
- pf_core-0.1.0/tests/test_pipeline_baseline_diff.py +236 -0
- pf_core-0.1.0/tests/test_pipeline_cache.py +292 -0
- pf_core-0.1.0/tests/test_pipeline_resume.py +220 -0
- pf_core-0.1.0/tests/test_pipeline_run_record.py +203 -0
- pf_core-0.1.0/tests/test_pipeline_sequencer.py +171 -0
- pf_core-0.1.0/tests/test_pricing.py +137 -0
- pf_core-0.1.0/tests/test_prompts.py +298 -0
- pf_core-0.1.0/tests/test_pyproject_tiers.py +108 -0
- pf_core-0.1.0/tests/test_rate_limit.py +107 -0
- pf_core-0.1.0/tests/test_relative_dates.py +229 -0
- pf_core-0.1.0/tests/test_repository.py +84 -0
- pf_core-0.1.0/tests/test_service_base.py +102 -0
- pf_core-0.1.0/tests/test_similarity.py +106 -0
- pf_core-0.1.0/tests/test_soft_delete.py +162 -0
- pf_core-0.1.0/tests/test_templates.py +57 -0
- pf_core-0.1.0/tests/test_testing_plugin.py +207 -0
- pf_core-0.1.0/tests/test_url_liveness.py +263 -0
- pf_core-0.1.0/tests/test_urls.py +808 -0
- pf_core-0.1.0/tests/test_utils_env.py +149 -0
- pf_core-0.1.0/tests/test_utils_hashing.py +39 -0
- pf_core-0.1.0/tests/test_utils_io.py +170 -0
- pf_core-0.1.0/tests/test_utils_periods.py +232 -0
- pf_core-0.1.0/tests/test_utils_phash.py +167 -0
- pf_core-0.1.0/tests/test_utils_throttle.py +72 -0
- pf_core-0.1.0/tests/test_utils_vocab.py +181 -0
- pf_core-0.1.0/tests/test_version.py +23 -0
- pf_core-0.1.0/tests/test_web_helpers.py +59 -0
- 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)**.
|
pf_core-0.1.0/README.md
ADDED
|
@@ -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"]
|
pf_core-0.1.0/setup.cfg
ADDED
|
@@ -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()
|