pycatalyst 0.0.2__tar.gz → 0.0.7.dev0__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.
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/.github/workflows/publish.yml +1 -1
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/.github/workflows/test.yml +5 -5
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/.gitignore +15 -0
- {pycatalyst-0.0.2/src/pycatalyst.egg-info → pycatalyst-0.0.7.dev0}/PKG-INFO +7 -2
- pycatalyst-0.0.7.dev0/pycatalyst.cfg +67 -0
- pycatalyst-0.0.7.dev0/pycatalyst.cfg.example +67 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/pyproject.toml +13 -1
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/setup.sh +0 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/dependencies/__init__.py +8 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/dependencies/auth.py +143 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/dependencies/database.py +131 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/main.py +66 -7
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/models/__init__.py +8 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/models/requests.py +12 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/models/responses.py +20 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/routes/v1/auth.py +85 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/routes/v1/settings.py +27 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/api/routes/v1/workbench.py +528 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/cli.py +33 -3
- pycatalyst-0.0.7.dev0/src/pycatalyst/config/__init__.py +37 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/config/auth.py +68 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/config/database.py +36 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/config/paths.py +56 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/config/ui.py +56 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/config/workbench.py +147 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/data/seed/.gitkeep +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/__init__.py +26 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/base.py +62 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/cli.py +531 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/config.py +109 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/migrations/__init__.py +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/migrations/env.py +62 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/migrations/versions/20260101000000_initial.py +53 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/migrations/versions/__init__.py +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/models/__init__.py +7 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/models/app_metadata.py +21 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/db/paths.py +35 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/services/__init__.py +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/services/backends/__init__.py +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/services/backends/base.py +152 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/services/backends/docker_backend.py +283 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/services/backends/venv_backend.py +305 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/services/workbench_manager.py +400 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/.eslintrc.json +3 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/.npmrc +3 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/__init__.py +3 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/build.py +42 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/components.json +8 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/dev.py +40 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/next-env.d.ts +6 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/next.config.js +16 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/node_modules/flatted/python/flatted.py +149 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/404/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/404.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/__next.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/0575f275776d153c.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/0664a23cd6a5f7ba.js +34 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/31674d9a34dac40b.js +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/33dc0ae90f863979.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/3db2622c9e17684f.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/48f408d5c9d2a0ba.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/4a93e9c0c43d24a7.css +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/612195d87a9f708e.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/826946f349a354cd.js +24 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/82abf2d65f5428ae.js +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/88a6dfcd964f4302.js +73 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/ab52a6590efbe138.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/ace5f280042c2f7a.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/b7b85a15f387ab7c.css +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/b82d3555a0c5ad54.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/c0645d6fea057384.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/c6ac0e3a3d0e355f.js +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/cc784667b24a8c60.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/cd046009fa99a225.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/d2be314c3ece3fbe.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/de634c2407095732.js +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/f2f58a7e93290fbb.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/ff1a16fafef87110.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/chunks/turbopack-dc3a2def751ab70b.js +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/xwVOo753r2AQr1Ieeepou/_buildManifest.js +11 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/xwVOo753r2AQr1Ieeepou/_clientMiddlewareManifest.json +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_next/static/xwVOo753r2AQr1Ieeepou/_ssgManifest.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/__next._full.txt +19 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/__next._not-found.__PAGE__.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/__next._not-found.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/_not-found/index.txt +19 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/__next.documentation.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/__next.documentation.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/documentation/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/__next.generate.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/__next.generate.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/generate/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/__next.login.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/__next.login.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/login/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/__next.recipes.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/__next.recipes.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/recipes/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/__next.schemas.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/__next.schemas.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/schemas/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/__next.settings.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/__next.settings.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/settings/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/__next.sinks.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/__next.sinks.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/sinks/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/__next._full.txt +23 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/__next._tree.txt +3 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/__next.workbench.__PAGE__.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/__next.workbench.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/out/workbench/index.txt +23 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/package-lock.json +6687 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/package.json +39 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/postcss.config.js +6 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/server.py +218 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/documentation/page.tsx +212 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/error.tsx +31 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/generate/page.tsx +303 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/globals.css +80 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/layout.tsx +39 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/loading.tsx +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/login/page.tsx +102 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/page.tsx +93 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/recipes/page.tsx +220 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/schemas/page.tsx +210 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/settings/page.tsx +102 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/sinks/page.tsx +125 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/themes.css +183 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/app/workbench/page.tsx +626 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ThemeFromConfig.tsx +41 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ThemeSync.tsx +46 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/common/ErrorBoundary.tsx +41 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/common/ErrorDisplay.tsx +19 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/common/LoadingSpinner.tsx +15 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/common/index.ts +3 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/ApiUnavailableBanner.tsx +20 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/AppShell.tsx +20 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/AuthGuard.tsx +35 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/Navigation.tsx +187 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/PageContainer.tsx +20 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/PageHeader.tsx +19 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/layout/index.ts +6 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/alert.tsx +58 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/badge.tsx +32 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/button.tsx +54 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/card.tsx +79 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/input.tsx +24 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/label.tsx +23 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/skeleton.tsx +15 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/switch.tsx +43 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/tabs.tsx +94 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/ui/textarea.tsx +23 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/workbench/ContainerDialog.tsx +70 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/workbench/TerminalPanel.tsx +337 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/components/workbench/WorkbenchToolbar.tsx +219 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/hooks/use-auth-init.ts +24 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/api.ts +155 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/auth-storage.ts +26 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/constants.ts +42 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/settings.ts +46 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/terminal-theme.ts +131 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/types/index.ts +71 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/utils.ts +92 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/workbench-storage.ts +74 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/lib/workbench.ts +191 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/stores/app-config.ts +21 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/stores/auth.ts +76 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/stores/index.ts +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/src/types/global.d.ts +11 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/404/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/404.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/__next.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/0575f275776d153c.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/0664a23cd6a5f7ba.js +34 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/31674d9a34dac40b.js +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/33dc0ae90f863979.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/3db2622c9e17684f.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/48f408d5c9d2a0ba.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/4a93e9c0c43d24a7.css +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/612195d87a9f708e.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/826946f349a354cd.js +24 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/82abf2d65f5428ae.js +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/88a6dfcd964f4302.js +73 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/ab52a6590efbe138.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/ace5f280042c2f7a.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/b7b85a15f387ab7c.css +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/b82d3555a0c5ad54.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/c0645d6fea057384.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/c6ac0e3a3d0e355f.js +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/cc784667b24a8c60.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/cd046009fa99a225.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/d2be314c3ece3fbe.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/de634c2407095732.js +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/f2f58a7e93290fbb.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/ff1a16fafef87110.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/chunks/turbopack-dc3a2def751ab70b.js +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/xwVOo753r2AQr1Ieeepou/_buildManifest.js +11 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/xwVOo753r2AQr1Ieeepou/_clientMiddlewareManifest.json +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_next/static/xwVOo753r2AQr1Ieeepou/_ssgManifest.js +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/__next._full.txt +19 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/__next._not-found.__PAGE__.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/__next._not-found.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/_not-found/index.txt +19 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/__next.documentation.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/__next.documentation.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/documentation/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/__next.generate.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/__next.generate.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/generate/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/__next.login.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/__next.login.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/login/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/__next.recipes.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/__next.recipes.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/recipes/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/__next.schemas.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/__next.schemas.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/schemas/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/__next.settings.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/__next.settings.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/settings/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/__next._full.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/__next._tree.txt +2 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/__next.sinks.__PAGE__.txt +9 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/__next.sinks.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/sinks/index.txt +22 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/__next._full.txt +23 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/__next._head.txt +5 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/__next._index.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/__next._tree.txt +3 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/__next.workbench.__PAGE__.txt +10 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/__next.workbench.txt +4 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/index.html +1 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/static/workbench/index.txt +23 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/tailwind.config.js +74 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/tsconfig.json +42 -0
- pycatalyst-0.0.7.dev0/src/pycatalyst/ui/tsconfig.tsbuildinfo +1 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0/src/pycatalyst.egg-info}/PKG-INFO +7 -2
- pycatalyst-0.0.7.dev0/src/pycatalyst.egg-info/SOURCES.txt +404 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst.egg-info/requires.txt +8 -1
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_api.py +10 -4
- pycatalyst-0.0.7.dev0/tests/unit/test_scrollback_buffer.py +77 -0
- pycatalyst-0.0.7.dev0/tests/unit/test_venv_backend.py +223 -0
- pycatalyst-0.0.7.dev0/tests/unit/test_workbench_manager.py +319 -0
- pycatalyst-0.0.7.dev0/tests/unit/test_workbench_routes.py +378 -0
- pycatalyst-0.0.2/src/pycatalyst/worker/__init__.py +0 -0
- pycatalyst-0.0.2/src/pycatalyst.egg-info/SOURCES.txt +0 -78
- pycatalyst-0.0.2/tests/integration/__init__.py +0 -0
- pycatalyst-0.0.2/tests/unit/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/.pre-commit-config.yaml +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/CHANGELOG.md +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/CONTRIBUTING.md +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/LICENSE +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/Makefile +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/README.md +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/docs/PUBLISHING.md +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/scripts/publish-once.sh +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/setup.cfg +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/_protocols.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/_types.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/routes/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/routes/v1/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/routes/v1/generate.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/routes/v1/infer.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/api/routes/v1/recipes.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/data/templates/recipes/pycharter_contracts.yaml +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/data/templates/recipes/pyoptima_portfolio.yaml +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/data/templates/recipes/pystator_events.yaml +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/errors.py +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/config → pycatalyst-0.0.7.dev0/src/pycatalyst/generators}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/generators/compound.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/generators/engine.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/generators/registry.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/generators/scalar.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/py.typed +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/db → pycatalyst-0.0.7.dev0/src/pycatalyst/recipes}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/recipes/loader.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/recipes/models.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/recipes/runner.py +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/generators → pycatalyst-0.0.7.dev0/src/pycatalyst/schema}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/schema/inferrer.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/schema/json_schema.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/schema/pydantic.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/schema/spec.py +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/recipes → pycatalyst-0.0.7.dev0/src/pycatalyst/sinks}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/sinks/database.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/sinks/file.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/sinks/http.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/sinks/kafka.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/sinks/memory.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst/sinks/rabbitmq.py +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/schema → pycatalyst-0.0.7.dev0/src/pycatalyst/worker}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst.egg-info/dependency_links.txt +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst.egg-info/entry_points.txt +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/src/pycatalyst.egg-info/top_level.txt +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/conftest.py +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/sinks → pycatalyst-0.0.7.dev0/tests/integration}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/test_version.py +0 -0
- {pycatalyst-0.0.2/src/pycatalyst/ui → pycatalyst-0.0.7.dev0/tests/unit}/__init__.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_database_sink.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_errors.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_file_sink.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_generators.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_http_sink.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_kafka_sink.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_protocols.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_rabbitmq_sink.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_recipes.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_schema.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_sinks.py +0 -0
- {pycatalyst-0.0.2 → pycatalyst-0.0.7.dev0}/tests/unit/test_types.py +0 -0
|
@@ -11,10 +11,10 @@ jobs:
|
|
|
11
11
|
fail-fast: false
|
|
12
12
|
matrix:
|
|
13
13
|
python-version: ['3.11', '3.12', '3.13']
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
steps:
|
|
16
16
|
- uses: actions/checkout@v4
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
- name: Set up Python ${{ matrix.python-version }}
|
|
19
19
|
uses: actions/setup-python@v5
|
|
20
20
|
with:
|
|
@@ -25,11 +25,11 @@ jobs:
|
|
|
25
25
|
- name: Install dependencies
|
|
26
26
|
run: |
|
|
27
27
|
python -m pip install --upgrade pip
|
|
28
|
-
pip install -e ".[dev
|
|
29
|
-
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
|
|
30
30
|
- name: Run tests
|
|
31
31
|
run: pytest
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
- name: Run linting
|
|
34
34
|
run: |
|
|
35
35
|
ruff format --check src/pycatalyst tests
|
|
@@ -33,3 +33,18 @@ htmlcov/
|
|
|
33
33
|
# OS
|
|
34
34
|
.DS_Store
|
|
35
35
|
Thumbs.db
|
|
36
|
+
|
|
37
|
+
# Docs build (MkDocs)
|
|
38
|
+
site/
|
|
39
|
+
|
|
40
|
+
# Next.js / Node UI (if added)
|
|
41
|
+
src/pycatalyst/ui/.next/
|
|
42
|
+
src/pycatalyst/ui/out/
|
|
43
|
+
src/pycatalyst/ui/node_modules/
|
|
44
|
+
src/pycatalyst/ui/.env*.local
|
|
45
|
+
src/pycatalyst/ui/static/
|
|
46
|
+
|
|
47
|
+
# Local SQLite and temp
|
|
48
|
+
*.db
|
|
49
|
+
tmp/
|
|
50
|
+
*.log
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycatalyst
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7.dev0
|
|
4
4
|
Summary: A schema-aware data generation and testing platform for Python
|
|
5
5
|
Author-email: StatFYI <contact@statfyi.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -54,6 +54,11 @@ Requires-Dist: fastapi>=0.104.0; extra == "ui"
|
|
|
54
54
|
Requires-Dist: uvicorn[standard]>=0.24.0; extra == "ui"
|
|
55
55
|
Requires-Dist: httpx>=0.24.0; extra == "ui"
|
|
56
56
|
Requires-Dist: aiofiles>=23.0.0; extra == "ui"
|
|
57
|
+
Provides-Extra: workbench
|
|
58
|
+
Requires-Dist: pycatalyst[api,sandbox]; extra == "workbench"
|
|
59
|
+
Requires-Dist: ipython>=8.0.0; extra == "workbench"
|
|
60
|
+
Provides-Extra: sandbox
|
|
61
|
+
Requires-Dist: docker>=7.0.0; extra == "sandbox"
|
|
57
62
|
Provides-Extra: db
|
|
58
63
|
Requires-Dist: alembic>=1.13.0; extra == "db"
|
|
59
64
|
Requires-Dist: sqlalchemy>=2.0.0; extra == "db"
|
|
@@ -63,7 +68,7 @@ Provides-Extra: docs
|
|
|
63
68
|
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
64
69
|
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "docs"
|
|
65
70
|
Provides-Extra: all
|
|
66
|
-
Requires-Dist: pycatalyst[api,db,docs,faker,http,inference,parquet,postgres,ui,worker]; extra == "all"
|
|
71
|
+
Requires-Dist: pycatalyst[api,db,docs,faker,http,inference,parquet,postgres,sandbox,ui,workbench,worker]; extra == "all"
|
|
67
72
|
Provides-Extra: dev
|
|
68
73
|
Requires-Dist: pycatalyst[all]; extra == "dev"
|
|
69
74
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# PyCatalyst Configuration
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to pycatalyst.cfg and update with your values.
|
|
4
|
+
# Config keys use the same names as environment variables (e.g. PYCATALYST_DATABASE_URL).
|
|
5
|
+
# Environment variables override values in this file.
|
|
6
|
+
#
|
|
7
|
+
# Config file search order: current directory, ~/.pycatalyst/, package directory,
|
|
8
|
+
# then project root (directory containing alembic.ini).
|
|
9
|
+
#
|
|
10
|
+
# For UI dev/serve, the API URL is taken from PYCATALYST_API_URL (environment only;
|
|
11
|
+
# not read from this file). Default: http://localhost:8006
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# [database]
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# Database URL for API and db CLI (init, upgrade, seed). Required for persistence.
|
|
17
|
+
|
|
18
|
+
[database]
|
|
19
|
+
# PYCATALYST_DATABASE_URL = sqlite:///pycatalyst.db
|
|
20
|
+
PYCATALYST_DATABASE_URL = postgresql://postgres:1234567890@localhost:5432/postgres
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# [auth]
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# API authentication. Set PYCATALYST_AUTH_DISABLED = 1 to disable (all routes open).
|
|
26
|
+
|
|
27
|
+
[auth]
|
|
28
|
+
# PYCATALYST_AUTH_DISABLED = 0
|
|
29
|
+
PYCATALYST_AUTH_INITIAL_USERNAME = admin
|
|
30
|
+
PYCATALYST_AUTH_INITIAL_PASSWORD = changeme
|
|
31
|
+
PYCATALYST_AUTH_JWT_SECRET = your-secret-change-in-production
|
|
32
|
+
# Optional: external auth service (token introspect)
|
|
33
|
+
# PYCATALYST_AUTH_SERVICE_URL = https://auth.example.com
|
|
34
|
+
# PYCATALYST_AUTH_SERVICE_INTROSPECT_PATH = /introspect
|
|
35
|
+
|
|
36
|
+
# =============================================================================
|
|
37
|
+
# [ui]
|
|
38
|
+
# =============================================================================
|
|
39
|
+
# Global UI theme and app name when the UI is served (pycatalyst ui serve / ui dev).
|
|
40
|
+
# Used by the API app-config endpoint and by the static UI when config is injected.
|
|
41
|
+
|
|
42
|
+
[ui]
|
|
43
|
+
# Theme: light, dark, system, or named palettes (ocean, forest, sunset,
|
|
44
|
+
# high-contrast, sepia, violet, cathay).
|
|
45
|
+
PYCATALYST_UI_THEME = violet
|
|
46
|
+
|
|
47
|
+
# Website / app name shown in the UI (default: PyCatalyst).
|
|
48
|
+
PYCATALYST_UI_APP_NAME = PyCatalyst
|
|
49
|
+
|
|
50
|
+
# =============================================================================
|
|
51
|
+
# [workbench]
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# Web terminal workbench for interactive Python development and library testing.
|
|
54
|
+
|
|
55
|
+
[workbench]
|
|
56
|
+
# Max concurrent terminal sessions per environment (default: 10)
|
|
57
|
+
# PYCATALYST_WORKBENCH_MAX_SESSIONS = 10
|
|
58
|
+
# Default shell command (default: auto-detect bash/zsh/sh)
|
|
59
|
+
# PYCATALYST_WORKBENCH_SHELL = /bin/bash
|
|
60
|
+
# Directory for venv environments (default: ~/.pycatalyst/workbench/)
|
|
61
|
+
# PYCATALYST_WORKBENCH_VENV_DIR = ~/.pycatalyst/workbench
|
|
62
|
+
# Max environments (default: 5)
|
|
63
|
+
# PYCATALYST_WORKBENCH_MAX_ENVIRONMENTS = 5
|
|
64
|
+
# Docker image for container environments (default: python:3.13-slim)
|
|
65
|
+
# PYCATALYST_WORKBENCH_DOCKER_IMAGE = python:3.13-slim
|
|
66
|
+
# Docker container memory limit (default: 512m)
|
|
67
|
+
# PYCATALYST_WORKBENCH_DOCKER_MEM_LIMIT = 512m
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# PyCatalyst Configuration
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to pycatalyst.cfg and update with your values.
|
|
4
|
+
# Config keys use the same names as environment variables (e.g. PYCATALYST_DATABASE_URL).
|
|
5
|
+
# Environment variables override values in this file.
|
|
6
|
+
#
|
|
7
|
+
# Config file search order: current directory, ~/.pycatalyst/, package directory,
|
|
8
|
+
# then project root (directory containing alembic.ini).
|
|
9
|
+
#
|
|
10
|
+
# For UI dev/serve, the API URL is taken from PYCATALYST_API_URL (environment only;
|
|
11
|
+
# not read from this file). Default: http://localhost:8006
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# [database]
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# Database URL for API and db CLI (init, upgrade, seed). Required for persistence.
|
|
17
|
+
|
|
18
|
+
[database]
|
|
19
|
+
# PYCATALYST_DATABASE_URL = sqlite:///pycatalyst.db
|
|
20
|
+
# PYCATALYST_DATABASE_URL = postgresql://user:password@localhost:5432/pycatalyst
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# [auth]
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# API authentication. Set PYCATALYST_AUTH_DISABLED = 1 to disable (all routes open).
|
|
26
|
+
|
|
27
|
+
[auth]
|
|
28
|
+
# PYCATALYST_AUTH_DISABLED = 0
|
|
29
|
+
# PYCATALYST_AUTH_INITIAL_USERNAME = admin
|
|
30
|
+
# PYCATALYST_AUTH_INITIAL_PASSWORD = changeme
|
|
31
|
+
# PYCATALYST_AUTH_JWT_SECRET = your-secret-change-in-production
|
|
32
|
+
# Optional: external auth service (token introspect)
|
|
33
|
+
# PYCATALYST_AUTH_SERVICE_URL = https://auth.example.com
|
|
34
|
+
# PYCATALYST_AUTH_SERVICE_INTROSPECT_PATH = /introspect
|
|
35
|
+
|
|
36
|
+
# =============================================================================
|
|
37
|
+
# [ui]
|
|
38
|
+
# =============================================================================
|
|
39
|
+
# Global UI theme and app name when the UI is served (pycatalyst ui serve / ui dev).
|
|
40
|
+
# Used by the API app-config endpoint and by the static UI when config is injected.
|
|
41
|
+
|
|
42
|
+
[ui]
|
|
43
|
+
# Theme: light, dark, system, or named palettes (ocean, forest, sunset,
|
|
44
|
+
# high-contrast, sepia, violet, cathay).
|
|
45
|
+
# PYCATALYST_UI_THEME = light
|
|
46
|
+
|
|
47
|
+
# Website / app name shown in the UI (default: PyCatalyst).
|
|
48
|
+
# PYCATALYST_UI_APP_NAME = PyCatalyst
|
|
49
|
+
|
|
50
|
+
# =============================================================================
|
|
51
|
+
# [workbench]
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# Web terminal workbench for interactive Python development and library testing.
|
|
54
|
+
|
|
55
|
+
[workbench]
|
|
56
|
+
# Max concurrent terminal sessions per environment (default: 10)
|
|
57
|
+
# PYCATALYST_WORKBENCH_MAX_SESSIONS = 10
|
|
58
|
+
# Default shell command (default: auto-detect bash/zsh/sh)
|
|
59
|
+
# PYCATALYST_WORKBENCH_SHELL = /bin/bash
|
|
60
|
+
# Directory for venv environments (default: ~/.pycatalyst/workbench/)
|
|
61
|
+
# PYCATALYST_WORKBENCH_VENV_DIR = ~/.pycatalyst/workbench
|
|
62
|
+
# Max environments (default: 5)
|
|
63
|
+
# PYCATALYST_WORKBENCH_MAX_ENVIRONMENTS = 5
|
|
64
|
+
# Docker image for container environments (default: python:3.13-slim)
|
|
65
|
+
# PYCATALYST_WORKBENCH_DOCKER_IMAGE = python:3.13-slim
|
|
66
|
+
# Docker container memory limit (default: 512m)
|
|
67
|
+
# PYCATALYST_WORKBENCH_DOCKER_MEM_LIMIT = 512m
|
|
@@ -109,6 +109,17 @@ ui = [
|
|
|
109
109
|
"aiofiles>=23.0.0",
|
|
110
110
|
]
|
|
111
111
|
|
|
112
|
+
# --- Workbench (web terminal for interactive testing) ---
|
|
113
|
+
workbench = [
|
|
114
|
+
"pycatalyst[api,sandbox]",
|
|
115
|
+
"ipython>=8.0.0",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
# --- Sandbox (Docker container isolation for workbench) ---
|
|
119
|
+
sandbox = [
|
|
120
|
+
"docker>=7.0.0",
|
|
121
|
+
]
|
|
122
|
+
|
|
112
123
|
# --- Package-specific extras ---
|
|
113
124
|
db = [
|
|
114
125
|
"alembic>=1.13.0",
|
|
@@ -125,7 +136,7 @@ docs = [
|
|
|
125
136
|
|
|
126
137
|
# --- Aggregate extras ---
|
|
127
138
|
all = [
|
|
128
|
-
"pycatalyst[inference,faker,parquet,http,api,worker,ui,db,postgres,docs]",
|
|
139
|
+
"pycatalyst[inference,faker,parquet,http,api,worker,ui,workbench,sandbox,db,postgres,docs]",
|
|
129
140
|
]
|
|
130
141
|
dev = [
|
|
131
142
|
"pycatalyst[all]",
|
|
@@ -158,6 +169,7 @@ pycatalyst = [
|
|
|
158
169
|
"db/migrations/README",
|
|
159
170
|
# Recipe templates and seed data
|
|
160
171
|
"data/templates/recipes/*.yaml",
|
|
172
|
+
"data/seed/.gitkeep",
|
|
161
173
|
"data/seed/*.yaml",
|
|
162
174
|
# UI static files (build with: pycatalyst ui build)
|
|
163
175
|
"ui/static/*",
|
|
File without changes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication dependency for API routes.
|
|
3
|
+
|
|
4
|
+
Mirrors pystator: initial credentials from env or pycatalyst.cfg; JWT issued and verified in-process.
|
|
5
|
+
Optional auth service: token introspect via HTTP. When auth is disabled, get_current_user returns anonymous.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hmac
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import jwt
|
|
15
|
+
from fastapi import Depends, HTTPException, status
|
|
16
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
17
|
+
|
|
18
|
+
from pycatalyst.config.auth import (
|
|
19
|
+
get_auth_initial_credentials,
|
|
20
|
+
get_auth_jwt_secret,
|
|
21
|
+
get_auth_service_introspect_path,
|
|
22
|
+
get_auth_service_url,
|
|
23
|
+
is_auth_disabled,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
security = HTTPBearer(auto_error=False)
|
|
27
|
+
|
|
28
|
+
ACCESS_TOKEN_EXPIRE_SECONDS = 3600 # 1 hour
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _constant_time_compare(a: str, b: str) -> bool:
|
|
32
|
+
"""Constant-time string comparison to avoid timing attacks."""
|
|
33
|
+
return hmac.compare_digest(a.encode("utf-8"), b.encode("utf-8"))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def verify_initial_credentials(username: str, password: str) -> bool:
|
|
37
|
+
"""Verify username/password against initial credentials. Returns True if valid."""
|
|
38
|
+
creds = get_auth_initial_credentials()
|
|
39
|
+
if not creds:
|
|
40
|
+
return False
|
|
41
|
+
u, p = creds
|
|
42
|
+
return _constant_time_compare(username, u) and _constant_time_compare(password, p)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_access_token(
|
|
46
|
+
username: str, expires_delta_seconds: int = ACCESS_TOKEN_EXPIRE_SECONDS
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Create a JWT access token for the given username."""
|
|
49
|
+
secret = get_auth_jwt_secret()
|
|
50
|
+
if not secret:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"JWT secret not configured (set PYCATALYST_AUTH_JWT_SECRET in env or pycatalyst.cfg [auth])"
|
|
53
|
+
)
|
|
54
|
+
payload = {
|
|
55
|
+
"sub": username,
|
|
56
|
+
"exp": int(time.time()) + expires_delta_seconds,
|
|
57
|
+
"iat": int(time.time()),
|
|
58
|
+
}
|
|
59
|
+
return jwt.encode(payload, secret, algorithm="HS256")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def verify_jwt(token: str) -> dict[str, Any] | None:
|
|
63
|
+
"""Verify JWT and return payload (with 'sub' = username) or None if invalid."""
|
|
64
|
+
secret = get_auth_jwt_secret()
|
|
65
|
+
if not secret:
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
payload = jwt.decode(token, secret, algorithms=["HS256"])
|
|
69
|
+
return payload
|
|
70
|
+
except jwt.PyJWTError:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def introspect_token(token: str) -> dict[str, Any] | None:
|
|
75
|
+
"""Call auth service introspect endpoint. Return payload if active, else None."""
|
|
76
|
+
base = get_auth_service_url()
|
|
77
|
+
path = get_auth_service_introspect_path()
|
|
78
|
+
if not base or not path:
|
|
79
|
+
return None
|
|
80
|
+
url = f"{base.rstrip('/')}{path}"
|
|
81
|
+
try:
|
|
82
|
+
import httpx
|
|
83
|
+
|
|
84
|
+
async with httpx.AsyncClient() as client:
|
|
85
|
+
r = await client.post(
|
|
86
|
+
url,
|
|
87
|
+
data={"token": token},
|
|
88
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
89
|
+
timeout=5.0,
|
|
90
|
+
)
|
|
91
|
+
if r.status_code != 200:
|
|
92
|
+
return None
|
|
93
|
+
data = r.json()
|
|
94
|
+
if not data.get("active", False):
|
|
95
|
+
return None
|
|
96
|
+
return data
|
|
97
|
+
except Exception:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def get_current_user(
|
|
102
|
+
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
"""
|
|
105
|
+
Resolve current user from Bearer token. Uses JWT verify or auth service introspect.
|
|
106
|
+
When auth is disabled, returns a dummy user so routes do not need to branch.
|
|
107
|
+
"""
|
|
108
|
+
if is_auth_disabled():
|
|
109
|
+
return {"username": "anonymous", "auth_disabled": True}
|
|
110
|
+
|
|
111
|
+
token = None
|
|
112
|
+
if credentials and credentials.credentials:
|
|
113
|
+
token = credentials.credentials
|
|
114
|
+
|
|
115
|
+
if not token:
|
|
116
|
+
raise HTTPException(
|
|
117
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
118
|
+
detail="Not authenticated",
|
|
119
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
payload = verify_jwt(token)
|
|
123
|
+
if payload is None and (
|
|
124
|
+
get_auth_service_url() and get_auth_service_introspect_path()
|
|
125
|
+
):
|
|
126
|
+
payload = await introspect_token(token)
|
|
127
|
+
|
|
128
|
+
if payload is None:
|
|
129
|
+
raise HTTPException(
|
|
130
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
131
|
+
detail="Invalid or expired token",
|
|
132
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
username = payload.get("sub") or payload.get("username")
|
|
136
|
+
if not username:
|
|
137
|
+
raise HTTPException(
|
|
138
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
139
|
+
detail="Invalid token payload",
|
|
140
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return {"username": username}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Database session dependency for API routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from fastapi import HTTPException, status
|
|
10
|
+
from sqlalchemy import create_engine, inspect, text
|
|
11
|
+
from sqlalchemy.orm import Session
|
|
12
|
+
|
|
13
|
+
from pycatalyst.config import set_database_url
|
|
14
|
+
from pycatalyst.db.base import Base, get_session
|
|
15
|
+
from pycatalyst.db.config import get_db_url, get_migrations_dir, is_default_db_url
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _ensure_sqlite_initialized(db_url: str) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Ensure SQLite database is initialized with all tables.
|
|
21
|
+
Auto-initializes SQLite if it doesn't exist or is uninitialized.
|
|
22
|
+
"""
|
|
23
|
+
if not db_url.startswith("sqlite://"):
|
|
24
|
+
return
|
|
25
|
+
import logging
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
try:
|
|
29
|
+
db_path = db_url[10:] if db_url.startswith("sqlite:///") else db_url
|
|
30
|
+
if db_path == ":memory:":
|
|
31
|
+
return
|
|
32
|
+
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
engine = create_engine(db_url)
|
|
34
|
+
inspector = inspect(engine)
|
|
35
|
+
if "app_metadata" not in inspector.get_table_names():
|
|
36
|
+
logger.info("Auto-initializing SQLite database: %s", db_url)
|
|
37
|
+
|
|
38
|
+
for table in Base.metadata.tables.values():
|
|
39
|
+
if table.schema == "pycatalyst":
|
|
40
|
+
table.schema = None
|
|
41
|
+
Base.metadata.create_all(engine)
|
|
42
|
+
try:
|
|
43
|
+
from alembic import command
|
|
44
|
+
from alembic.config import Config
|
|
45
|
+
|
|
46
|
+
versions_dir = get_migrations_dir() / "versions"
|
|
47
|
+
if versions_dir.exists() and any(versions_dir.iterdir()):
|
|
48
|
+
set_database_url(db_url)
|
|
49
|
+
cfg = Config()
|
|
50
|
+
cfg.set_main_option("script_location", str(get_migrations_dir()))
|
|
51
|
+
cfg.set_main_option("sqlalchemy.url", db_url)
|
|
52
|
+
command.upgrade(cfg, "head")
|
|
53
|
+
logger.info("SQLite database initialized with migrations")
|
|
54
|
+
else:
|
|
55
|
+
logger.info("SQLite database initialized with base tables")
|
|
56
|
+
except Exception:
|
|
57
|
+
logger.info("SQLite database initialized with base tables")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.warning("Could not auto-initialize SQLite database: %s", e)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_db_session() -> Generator[Session, None, None]:
|
|
63
|
+
"""
|
|
64
|
+
FastAPI dependency to get database session.
|
|
65
|
+
|
|
66
|
+
Defaults to SQLite (sqlite:///pycatalyst.db) if no database URL is configured.
|
|
67
|
+
Automatically initializes SQLite database if it doesn't exist or is uninitialized.
|
|
68
|
+
"""
|
|
69
|
+
import logging
|
|
70
|
+
|
|
71
|
+
logger = logging.getLogger(__name__)
|
|
72
|
+
db_url = get_db_url(required=False)
|
|
73
|
+
if is_default_db_url(db_url):
|
|
74
|
+
logger.warning(
|
|
75
|
+
"No database URL configured. Using default SQLite: %s\n"
|
|
76
|
+
"To use PostgreSQL, set PYCATALYST_DATABASE_URL:\n"
|
|
77
|
+
" export PYCATALYST_DATABASE_URL='postgresql://user:password@localhost:5432/pycatalyst'",
|
|
78
|
+
db_url,
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
masked_url = db_url
|
|
82
|
+
if "@" in db_url and "://" in db_url:
|
|
83
|
+
parts = db_url.split("@", 1)
|
|
84
|
+
if ":" in parts[0]:
|
|
85
|
+
user_pass = parts[0].split("://", 1)[1]
|
|
86
|
+
if ":" in user_pass:
|
|
87
|
+
user, _ = user_pass.split(":", 1)
|
|
88
|
+
masked_url = (
|
|
89
|
+
db_url.split(":", 2)[0] + "://" + user + ":****@" + parts[1]
|
|
90
|
+
)
|
|
91
|
+
logger.info("Using database: %s", masked_url)
|
|
92
|
+
_ensure_sqlite_initialized(db_url)
|
|
93
|
+
session = None
|
|
94
|
+
try:
|
|
95
|
+
session = get_session(db_url)
|
|
96
|
+
session.execute(text("SELECT 1"))
|
|
97
|
+
yield session
|
|
98
|
+
except Exception as e:
|
|
99
|
+
if session:
|
|
100
|
+
try:
|
|
101
|
+
session.rollback()
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
logger.error("Database session error: %s", e, exc_info=True)
|
|
105
|
+
error_detail = "Failed to connect to database"
|
|
106
|
+
error_msg = str(e).lower()
|
|
107
|
+
if "no such table" in error_msg or (
|
|
108
|
+
"table" in error_msg and "doesn't exist" in error_msg
|
|
109
|
+
):
|
|
110
|
+
error_detail = "Database tables not found. Run: pycatalyst db init"
|
|
111
|
+
elif "database is locked" in error_msg:
|
|
112
|
+
error_detail = (
|
|
113
|
+
"Database is locked. Close other connections or wait and try again."
|
|
114
|
+
)
|
|
115
|
+
elif "permission denied" in error_msg or "access denied" in error_msg:
|
|
116
|
+
error_detail = "Database permission denied. Check file permissions."
|
|
117
|
+
else:
|
|
118
|
+
if os.getenv("ENVIRONMENT") == "development" or not os.getenv(
|
|
119
|
+
"ENVIRONMENT"
|
|
120
|
+
):
|
|
121
|
+
error_detail = f"Failed to connect to database: {e}"
|
|
122
|
+
raise HTTPException(
|
|
123
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
124
|
+
detail=error_detail,
|
|
125
|
+
)
|
|
126
|
+
finally:
|
|
127
|
+
if session:
|
|
128
|
+
try:
|
|
129
|
+
session.close()
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
@@ -12,13 +12,14 @@ import os
|
|
|
12
12
|
from collections.abc import AsyncGenerator
|
|
13
13
|
from contextlib import asynccontextmanager
|
|
14
14
|
|
|
15
|
-
from fastapi import FastAPI, HTTPException, Request, status
|
|
15
|
+
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
|
16
16
|
from fastapi.exceptions import RequestValidationError
|
|
17
17
|
from fastapi.middleware.cors import CORSMiddleware
|
|
18
18
|
from fastapi.responses import JSONResponse
|
|
19
19
|
|
|
20
20
|
from pycatalyst import __version__ as catalyst_version
|
|
21
|
-
from pycatalyst.api.
|
|
21
|
+
from pycatalyst.api.dependencies.auth import get_current_user
|
|
22
|
+
from pycatalyst.api.routes.v1 import auth, generate, infer, recipes, settings, workbench
|
|
22
23
|
|
|
23
24
|
API_VERSION = "v1"
|
|
24
25
|
API_PREFIX = f"/api/{API_VERSION}"
|
|
@@ -27,7 +28,14 @@ API_PREFIX = f"/api/{API_VERSION}"
|
|
|
27
28
|
@asynccontextmanager
|
|
28
29
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
29
30
|
"""Lifespan context manager for FastAPI application."""
|
|
31
|
+
import asyncio
|
|
32
|
+
|
|
33
|
+
from pycatalyst.services.workbench_manager import workbench_manager
|
|
34
|
+
|
|
35
|
+
reaper_task = asyncio.create_task(workbench_manager.run_reaper())
|
|
30
36
|
yield
|
|
37
|
+
reaper_task.cancel()
|
|
38
|
+
workbench_manager.shutdown()
|
|
31
39
|
|
|
32
40
|
|
|
33
41
|
def create_application() -> FastAPI:
|
|
@@ -104,14 +112,65 @@ def create_application() -> FastAPI:
|
|
|
104
112
|
},
|
|
105
113
|
)
|
|
106
114
|
|
|
107
|
-
#
|
|
108
|
-
app.include_router(
|
|
109
|
-
|
|
110
|
-
app
|
|
115
|
+
# Auth router (login, logout, me)
|
|
116
|
+
app.include_router(auth.router, prefix=API_PREFIX, tags=["Auth"])
|
|
117
|
+
|
|
118
|
+
# Public settings (app-config for UI; no auth)
|
|
119
|
+
app.include_router(
|
|
120
|
+
settings.public_router,
|
|
121
|
+
prefix=API_PREFIX,
|
|
122
|
+
tags=["Settings"],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Protected routes: require valid Bearer when auth enabled
|
|
126
|
+
auth_dep = [Depends(get_current_user)]
|
|
127
|
+
app.include_router(
|
|
128
|
+
generate.router,
|
|
129
|
+
prefix=API_PREFIX,
|
|
130
|
+
tags=["generate"],
|
|
131
|
+
dependencies=auth_dep,
|
|
132
|
+
)
|
|
133
|
+
app.include_router(
|
|
134
|
+
infer.router,
|
|
135
|
+
prefix=API_PREFIX,
|
|
136
|
+
tags=["infer"],
|
|
137
|
+
dependencies=auth_dep,
|
|
138
|
+
)
|
|
139
|
+
app.include_router(
|
|
140
|
+
recipes.router,
|
|
141
|
+
prefix=API_PREFIX,
|
|
142
|
+
tags=["recipes"],
|
|
143
|
+
dependencies=auth_dep,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Workbench public (capabilities — no auth)
|
|
147
|
+
app.include_router(
|
|
148
|
+
workbench.public_router,
|
|
149
|
+
prefix=API_PREFIX,
|
|
150
|
+
tags=["workbench"],
|
|
151
|
+
)
|
|
152
|
+
# Workbench REST (environment/session CRUD) — protected by HTTPBearer auth
|
|
153
|
+
app.include_router(
|
|
154
|
+
workbench.router,
|
|
155
|
+
prefix=API_PREFIX,
|
|
156
|
+
tags=["workbench"],
|
|
157
|
+
dependencies=auth_dep,
|
|
158
|
+
)
|
|
159
|
+
# Workbench WebSocket — auth via token query param (HTTPBearer can't work over WS)
|
|
160
|
+
app.include_router(
|
|
161
|
+
workbench.ws_router,
|
|
162
|
+
prefix=API_PREFIX,
|
|
163
|
+
tags=["workbench"],
|
|
164
|
+
)
|
|
111
165
|
|
|
112
166
|
# Health check
|
|
113
167
|
@app.get("/health")
|
|
114
168
|
async def health() -> dict[str, str]:
|
|
115
|
-
|
|
169
|
+
out: dict[str, str] = {"status": "ok", "version": catalyst_version}
|
|
170
|
+
try:
|
|
171
|
+
out["workbench"] = workbench.workbench_manager.workbench_health()
|
|
172
|
+
except Exception:
|
|
173
|
+
out["workbench"] = "unavailable"
|
|
174
|
+
return out
|
|
116
175
|
|
|
117
176
|
return app
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Request and response models for PyCatalyst API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pycatalyst.api.models.requests import LoginRequest
|
|
6
|
+
from pycatalyst.api.models.responses import LoginResponse, UserResponse
|
|
7
|
+
|
|
8
|
+
__all__ = ["LoginRequest", "LoginResponse", "UserResponse"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Request models for PyCatalyst API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoginRequest(BaseModel):
|
|
9
|
+
"""Request body for authentication."""
|
|
10
|
+
|
|
11
|
+
username: str = Field(..., description="Username")
|
|
12
|
+
password: str = Field(..., description="Password")
|