stkit 17.0.0__tar.gz → 17.0.2__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.
- {stkit-17.0.0 → stkit-17.0.2}/CHANGELOG.md +21 -1
- {stkit-17.0.0 → stkit-17.0.2}/PKG-INFO +2 -3
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/uv.lock +24 -54
- {stkit-17.0.0 → stkit-17.0.2}/pyproject.toml +3 -4
- {stkit-17.0.0 → stkit-17.0.2}/stk/__init__.py +1 -1
- {stkit-17.0.0 → stkit-17.0.2}/stk/checks.py +3 -2
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/agent.py +4 -1
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/database.py +4 -1
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/main.py +13 -2
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/smoke.py +3 -3
- {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/__init__.py +9 -2
- {stkit-17.0.0 → stkit-17.0.2}/stk/public/views.py +4 -4
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +4 -1
- stkit-17.0.2/stk/scaffold/app/github/workflows/checks.yml +35 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/generator.py +1 -0
- stkit-17.0.2/tests/test_app_index.py +43 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_production_security.py +4 -4
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_scaffolder.py +7 -1
- {stkit-17.0.0 → stkit-17.0.2}/.gitignore +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/LICENSE +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/README.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/SECURITY.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/.env-sample +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/.gitignore +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/README.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/app.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/migrations/364232ae77d5_add_note.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/models.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/templates/note/index.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/note/views.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/settings.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/minimal/templates/layout.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/pyproject.toml +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/tests/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/examples/minimal/tests/test_app.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/__main__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/agent_login.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/app.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/base.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/develop.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/doctor.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/reports.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/users.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/cli/watch.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/extensions.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/env.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/script.py.mako +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/migrations/versions/stk_0001_initial_schema.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/portal/views.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/public/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/public/models.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/CLAUDE.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/Dockerfile +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/README.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/__app__/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/__app__/app.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/__app__/settings.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-api/SKILL.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/docker-compose.yml +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/dockerignore +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/env-sample +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/gitignore +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/nginx/nginx.conf +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/nginx/stk.conf +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/postgres/init-db.sh +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/pyproject.toml +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/tests/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/app/tests/test_app.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/scaffold/templates.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/security.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/settings.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/shell.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/VERSIONS.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/css/app.css +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/css/layout.css +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/css/vuetify.min.css +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/icons/fonts/tabler-icons.woff2 +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/icons/tabler-icons.min.css +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/img/auth-bg.webp +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/img/favicon.ico +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/img/stk.svg +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/axios.min.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/NotificationDropdown.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/ThemeSwitcher.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/TransitionExpand.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/VerticalNavGroup.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/VerticalNavLink.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/VerticalNavSectionTitle.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/components/index.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/config.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/vue.min.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/js/vuetify.min.js +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/static/robots.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/tasks/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/401.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/404.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/500.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/_brand.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/auth_layout.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/cms/activities.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/cms/roles.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/cms/users.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/dashboard.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/index.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/layout.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/login-layout.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/_macros.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/_messages.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/change_password.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/confirm_email.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/change_notice.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/change_notice.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/confirmation_instructions.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/confirmation_instructions.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/login_instructions.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/login_instructions.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_instructions.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_instructions.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_notice.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/reset_notice.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/welcome.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/email/welcome.txt +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/forgot_password.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/login_user.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/mf_recovery.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/mf_recovery_codes.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/register_user.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/reset_password.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/send_confirmation.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/send_login.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/two_factor_select.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/two_factor_setup.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/two_factor_verify_code.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/wan_register.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/wan_signin.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/templates/security/wan_verify.html +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/user/__init__.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/user/forms.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/user/models.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/user/views.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/utils/email.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/utils/ratelimit.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/stk/websocket.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_agent_operability.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_branding.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_builder_ux.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_cli.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_composition.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_csrf_exempt.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_error_handler.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_migration_drift.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_security_compatibility.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_shell.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_smoke_pages.py +0 -0
- {stkit-17.0.0 → stkit-17.0.2}/tests/test_static_versioning.py +0 -0
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v17.0.
|
|
3
|
+
## v17.0.2 (2026-10-25)
|
|
4
|
+
|
|
5
|
+
No migration required.
|
|
6
|
+
|
|
7
|
+
- `stk verify` follows redirects from `/` and checks that the page it lands on answers 200, so an app whose `/` sends people to sign-in or to its own home passes. A redirect to a broken page still fails.
|
|
8
|
+
- Replacing stk's landing view is documented: `app.view_functions["public.index"] = home` in an extension's `init_app` (Architecture, Public).
|
|
9
|
+
- `STK_CLI_SECTIONS` groups an app's own commands in `stk --help`, after stk's sections, instead of listing them all under Other.
|
|
10
|
+
|
|
11
|
+
## v17.0.1 (2026-10-25)
|
|
12
|
+
|
|
13
|
+
No migration required.
|
|
14
|
+
|
|
15
|
+
- Every dependency is locked at its latest release and tested: SQLAlchemy 2.1, Quart 0.23, webauthn 3, uvicorn 0.54, cryptography 50.0.2, and the rest. `pip-audit` finds no known vulnerabilities.
|
|
16
|
+
- Requires quart-security 2.0.3, which declares the passkey usage choices the way wtforms 3.3 expects (tuple choices are removed in wtforms 3.4).
|
|
17
|
+
- The pre-commit ruff hook matches the locked ruff (0.17.2). Ruff now also formats the Python samples in the docs.
|
|
18
|
+
|
|
19
|
+
## v17.0.0 (2026-10-25)
|
|
4
20
|
|
|
5
21
|
stk becomes an installed, versioned package. An app is a separate, thin package that composes it and never edits a framework file, so upgrading stk is a version bump plus whatever its release note says.
|
|
6
22
|
|
|
@@ -22,6 +38,10 @@ stk becomes an installed, versioned package. An app is a separate, thin package
|
|
|
22
38
|
- `stk db check` and `stk smoke` use a throwaway database of the configured dialect, so Postgres apps are checked on Postgres (the role needs CREATEDB). Autogenerate never offers to drop PostGIS's `spatial_ref_sys`. `stk.migrations.portable_tables()` returns the tables SQLite can build.
|
|
23
39
|
- Autogenerated revisions import what their column types need, and pass ruff without per-file exceptions.
|
|
24
40
|
- `stk doctor` reports the app it loaded and understands two lineages.
|
|
41
|
+
- `stk init` gives each app a `CLAUDE.md` (with `AGENTS.md` linked) carrying stk's rules and pointing at the docs, the stk skills, and a CI workflow running tests, `stk verify`, and `stk smoke`. `stk verify` in stk's repository fails when the template's rules drift from stk's.
|
|
42
|
+
- `stk new` uses English plurals for pages and APIs (`category` gives `/categories/`). Pass the singular name.
|
|
43
|
+
- stk moves from httpx to `httpx2`, with `authlib>=1.9.1` and `quart-security>=2.0.2` doing the same, so authlib no longer warns about its deprecated httpx fallback on every command.
|
|
44
|
+
- The unused `sidebar` and `layout_classes` blocks are gone from stk's templates and the scaffold.
|
|
25
45
|
|
|
26
46
|
### Upgrade considerations
|
|
27
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stkit
|
|
3
|
-
Version: 17.0.
|
|
3
|
+
Version: 17.0.2
|
|
4
4
|
Summary: An async Quart framework optimized for AI-assisted development workflows
|
|
5
5
|
Author-email: Level09 <hello@level09.com>
|
|
6
6
|
License: MIT
|
|
@@ -16,7 +16,6 @@ Requires-Dist: click>=8.3.1
|
|
|
16
16
|
Requires-Dist: cryptography>=45.0.5
|
|
17
17
|
Requires-Dist: email-validator>=2.2.0
|
|
18
18
|
Requires-Dist: httpx2>=2.16.0
|
|
19
|
-
Requires-Dist: httpx>=0.27.0
|
|
20
19
|
Requires-Dist: jinja2>=3.1.6
|
|
21
20
|
Requires-Dist: joserfc<2,>=1.7.1
|
|
22
21
|
Requires-Dist: libpass>=1.9.0
|
|
@@ -25,7 +24,7 @@ Requires-Dist: pillow>=13.0.0
|
|
|
25
24
|
Requires-Dist: psycopg2-binary>=2.9.10
|
|
26
25
|
Requires-Dist: python-dotenv>=1.1.1
|
|
27
26
|
Requires-Dist: qrcode>=8.2
|
|
28
|
-
Requires-Dist: quart-security<3,>=2.0.
|
|
27
|
+
Requires-Dist: quart-security<3,>=2.0.3
|
|
29
28
|
Requires-Dist: quart-session>=3.0.0
|
|
30
29
|
Requires-Dist: quart>=0.20.0
|
|
31
30
|
Requires-Dist: rich>=14.0.0
|
|
@@ -2,9 +2,12 @@ version = 1
|
|
|
2
2
|
revision = 3
|
|
3
3
|
requires-python = ">=3.11"
|
|
4
4
|
resolution-markers = [
|
|
5
|
-
"python_full_version >= '3.14'",
|
|
6
|
-
"python_full_version
|
|
7
|
-
"python_full_version == '3.
|
|
5
|
+
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
|
6
|
+
"python_full_version >= '3.14' and sys_platform != 'emscripten'",
|
|
7
|
+
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
|
|
8
|
+
"python_full_version == '3.13.*' and sys_platform != 'emscripten'",
|
|
9
|
+
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
|
|
10
|
+
"python_full_version == '3.12.*' and sys_platform != 'emscripten'",
|
|
8
11
|
"python_full_version < '3.12'",
|
|
9
12
|
]
|
|
10
13
|
|
|
@@ -366,15 +369,6 @@ wheels = [
|
|
|
366
369
|
{ url = "https://files.pythonhosted.org/packages/d4/c0/e27a1e516a89af7194fc497f4b96d9601771ca41bb66fd5738113df80282/cbor2-6.1.5-cp315-cp315t-win_arm64.whl", hash = "sha256:fd34b35b0a2b366f5b4bd53489ccd10d7576b0d4dd68db38ef64b4e617ea8f76", size = 294495, upload-time = "2026-10-01T18:09:32.192Z" },
|
|
367
370
|
]
|
|
368
371
|
|
|
369
|
-
[[package]]
|
|
370
|
-
name = "certifi"
|
|
371
|
-
version = "2026.10.23"
|
|
372
|
-
source = { registry = "https://pypi.org/simple" }
|
|
373
|
-
sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" }
|
|
374
|
-
wheels = [
|
|
375
|
-
{ url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" },
|
|
376
|
-
]
|
|
377
|
-
|
|
378
372
|
[[package]]
|
|
379
373
|
name = "cffi"
|
|
380
374
|
version = "2.1.1"
|
|
@@ -734,19 +728,6 @@ wheels = [
|
|
|
734
728
|
{ url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" },
|
|
735
729
|
]
|
|
736
730
|
|
|
737
|
-
[[package]]
|
|
738
|
-
name = "httpcore"
|
|
739
|
-
version = "1.0.9"
|
|
740
|
-
source = { registry = "https://pypi.org/simple" }
|
|
741
|
-
dependencies = [
|
|
742
|
-
{ name = "certifi" },
|
|
743
|
-
{ name = "h11" },
|
|
744
|
-
]
|
|
745
|
-
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
|
746
|
-
wheels = [
|
|
747
|
-
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
|
748
|
-
]
|
|
749
|
-
|
|
750
731
|
[[package]]
|
|
751
732
|
name = "httpcore2"
|
|
752
733
|
version = "2.16.0"
|
|
@@ -760,21 +741,6 @@ wheels = [
|
|
|
760
741
|
{ url = "https://files.pythonhosted.org/packages/af/c2/ce417c397afa5181f0e3c6ff01a4d01639414e1adef08730d97571297151/httpcore2-2.16.0-py3-none-any.whl", hash = "sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f", size = 85070, upload-time = "2026-10-21T07:09:42.802Z" },
|
|
761
742
|
]
|
|
762
743
|
|
|
763
|
-
[[package]]
|
|
764
|
-
name = "httpx"
|
|
765
|
-
version = "0.28.1"
|
|
766
|
-
source = { registry = "https://pypi.org/simple" }
|
|
767
|
-
dependencies = [
|
|
768
|
-
{ name = "anyio" },
|
|
769
|
-
{ name = "certifi" },
|
|
770
|
-
{ name = "httpcore" },
|
|
771
|
-
{ name = "idna" },
|
|
772
|
-
]
|
|
773
|
-
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
|
774
|
-
wheels = [
|
|
775
|
-
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
|
776
|
-
]
|
|
777
|
-
|
|
778
744
|
[[package]]
|
|
779
745
|
name = "httpx2"
|
|
780
746
|
version = "2.16.0"
|
|
@@ -1460,9 +1426,12 @@ name = "pyee"
|
|
|
1460
1426
|
version = "14.0.0"
|
|
1461
1427
|
source = { registry = "https://pypi.org/simple" }
|
|
1462
1428
|
resolution-markers = [
|
|
1463
|
-
"python_full_version >= '3.14'",
|
|
1464
|
-
"python_full_version
|
|
1465
|
-
"python_full_version == '3.
|
|
1429
|
+
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
|
1430
|
+
"python_full_version >= '3.14' and sys_platform != 'emscripten'",
|
|
1431
|
+
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
|
|
1432
|
+
"python_full_version == '3.13.*' and sys_platform != 'emscripten'",
|
|
1433
|
+
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
|
|
1434
|
+
"python_full_version == '3.12.*' and sys_platform != 'emscripten'",
|
|
1466
1435
|
]
|
|
1467
1436
|
dependencies = [
|
|
1468
1437
|
{ name = "typing-extensions" },
|
|
@@ -1601,7 +1570,8 @@ name = "quart"
|
|
|
1601
1570
|
version = "0.22.0"
|
|
1602
1571
|
source = { registry = "https://pypi.org/simple" }
|
|
1603
1572
|
resolution-markers = [
|
|
1604
|
-
"python_full_version == '3.12.*'",
|
|
1573
|
+
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
|
|
1574
|
+
"python_full_version == '3.12.*' and sys_platform != 'emscripten'",
|
|
1605
1575
|
"python_full_version < '3.12'",
|
|
1606
1576
|
]
|
|
1607
1577
|
dependencies = [
|
|
@@ -1625,8 +1595,10 @@ name = "quart"
|
|
|
1625
1595
|
version = "0.23.1"
|
|
1626
1596
|
source = { registry = "https://pypi.org/simple" }
|
|
1627
1597
|
resolution-markers = [
|
|
1628
|
-
"python_full_version >= '3.14'",
|
|
1629
|
-
"python_full_version
|
|
1598
|
+
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
|
1599
|
+
"python_full_version >= '3.14' and sys_platform != 'emscripten'",
|
|
1600
|
+
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
|
|
1601
|
+
"python_full_version == '3.13.*' and sys_platform != 'emscripten'",
|
|
1630
1602
|
]
|
|
1631
1603
|
dependencies = [
|
|
1632
1604
|
{ name = "aiofiles" },
|
|
@@ -1646,7 +1618,7 @@ wheels = [
|
|
|
1646
1618
|
|
|
1647
1619
|
[[package]]
|
|
1648
1620
|
name = "quart-security"
|
|
1649
|
-
version = "2.0.
|
|
1621
|
+
version = "2.0.3"
|
|
1650
1622
|
source = { registry = "https://pypi.org/simple" }
|
|
1651
1623
|
dependencies = [
|
|
1652
1624
|
{ name = "aiosmtplib" },
|
|
@@ -1655,7 +1627,7 @@ dependencies = [
|
|
|
1655
1627
|
{ name = "cbor2" },
|
|
1656
1628
|
{ name = "cryptography" },
|
|
1657
1629
|
{ name = "email-validator" },
|
|
1658
|
-
{ name = "
|
|
1630
|
+
{ name = "httpx2" },
|
|
1659
1631
|
{ name = "libpass", extra = ["bcrypt"] },
|
|
1660
1632
|
{ name = "pyotp" },
|
|
1661
1633
|
{ name = "qrcode", extra = ["pil"] },
|
|
@@ -1665,9 +1637,9 @@ dependencies = [
|
|
|
1665
1637
|
{ name = "webauthn" },
|
|
1666
1638
|
{ name = "wtforms" },
|
|
1667
1639
|
]
|
|
1668
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1640
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2b/88/43c45890dd40db6fa5e57d09705b35c42f3a36981997ab3269a2c33403a0/quart_security-2.0.3.tar.gz", hash = "sha256:74be1c5177afca54cc90bafd10d7ae69a1f4923e1786ff106fb9990dd8f7eb5c", size = 35687, upload-time = "2026-10-25T10:39:02.875Z" }
|
|
1669
1641
|
wheels = [
|
|
1670
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1642
|
+
{ url = "https://files.pythonhosted.org/packages/ef/b5/798c32a417ecba01152a4633cc4552b7ae0a4d70285f003ea7e5f1799e61/quart_security-2.0.3-py3-none-any.whl", hash = "sha256:08ac07b53242b70b519515299e89912cca2c21fabf12a6d25b87f6e4c8601268", size = 41815, upload-time = "2026-10-25T10:39:01.684Z" },
|
|
1671
1643
|
]
|
|
1672
1644
|
|
|
1673
1645
|
[[package]]
|
|
@@ -1819,7 +1791,7 @@ asyncio = [
|
|
|
1819
1791
|
|
|
1820
1792
|
[[package]]
|
|
1821
1793
|
name = "stkit"
|
|
1822
|
-
version = "17.0.
|
|
1794
|
+
version = "17.0.1"
|
|
1823
1795
|
source = { editable = "../../" }
|
|
1824
1796
|
dependencies = [
|
|
1825
1797
|
{ name = "aiosmtplib" },
|
|
@@ -1831,7 +1803,6 @@ dependencies = [
|
|
|
1831
1803
|
{ name = "click" },
|
|
1832
1804
|
{ name = "cryptography" },
|
|
1833
1805
|
{ name = "email-validator" },
|
|
1834
|
-
{ name = "httpx" },
|
|
1835
1806
|
{ name = "httpx2" },
|
|
1836
1807
|
{ name = "jinja2" },
|
|
1837
1808
|
{ name = "joserfc" },
|
|
@@ -1876,7 +1847,6 @@ requires-dist = [
|
|
|
1876
1847
|
{ name = "click", specifier = ">=8.3.1" },
|
|
1877
1848
|
{ name = "cryptography", specifier = ">=45.0.5" },
|
|
1878
1849
|
{ name = "email-validator", specifier = ">=2.2.0" },
|
|
1879
|
-
{ name = "httpx", specifier = ">=0.27.0" },
|
|
1880
1850
|
{ name = "httpx2", specifier = ">=2.16.0" },
|
|
1881
1851
|
{ name = "hypercorn", marker = "extra == 'asgi'", specifier = ">=0.17.0" },
|
|
1882
1852
|
{ name = "jinja2", specifier = ">=3.1.6" },
|
|
@@ -1891,7 +1861,7 @@ requires-dist = [
|
|
|
1891
1861
|
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
|
1892
1862
|
{ name = "qrcode", specifier = ">=8.2" },
|
|
1893
1863
|
{ name = "quart", specifier = ">=0.20.0" },
|
|
1894
|
-
{ name = "quart-security", specifier = ">=2.0.
|
|
1864
|
+
{ name = "quart-security", specifier = ">=2.0.3,<3" },
|
|
1895
1865
|
{ name = "quart-session", specifier = ">=3.0.0" },
|
|
1896
1866
|
{ name = "redis", marker = "extra == 'full'", specifier = ">=7.1.0" },
|
|
1897
1867
|
{ name = "rich", specifier = ">=14.0.0" },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stkit"
|
|
3
|
-
version = "17.0.
|
|
3
|
+
version = "17.0.2"
|
|
4
4
|
description = "An async Quart framework optimized for AI-assisted development workflows"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Level09", email = "hello@level09.com"}
|
|
@@ -19,8 +19,7 @@ dependencies = [
|
|
|
19
19
|
"click>=8.3.1",
|
|
20
20
|
"cryptography>=45.0.5",
|
|
21
21
|
"email-validator>=2.2.0",
|
|
22
|
-
"
|
|
23
|
-
"httpx2>=2.16.0", # authlib's HTTP client; plain httpx is its deprecated fallback
|
|
22
|
+
"httpx2>=2.16.0",
|
|
24
23
|
"jinja2>=3.1.6",
|
|
25
24
|
"joserfc>=1.7.1,<2",
|
|
26
25
|
"libpass>=1.9.0",
|
|
@@ -30,7 +29,7 @@ dependencies = [
|
|
|
30
29
|
"python-dotenv>=1.1.1",
|
|
31
30
|
"qrcode>=8.2",
|
|
32
31
|
"quart>=0.20.0",
|
|
33
|
-
"quart-security>=2.0.
|
|
32
|
+
"quart-security>=2.0.3,<3",
|
|
34
33
|
"quart-session>=3.0.0",
|
|
35
34
|
"rich>=14.0.0",
|
|
36
35
|
"sqlalchemy[asyncio]>=2.0.41",
|
|
@@ -286,10 +286,11 @@ def check_admin_reset_route(app):
|
|
|
286
286
|
)
|
|
287
287
|
|
|
288
288
|
|
|
289
|
-
@check("GET /
|
|
289
|
+
@check("GET / answers 200, after any redirects")
|
|
290
290
|
async def check_http_index(app):
|
|
291
|
+
# An app may send / to its sign-in or home page; what it lands on must work.
|
|
291
292
|
client = app.test_client()
|
|
292
|
-
response = await client.get("/")
|
|
293
|
+
response = await client.get("/", follow_redirects=True)
|
|
293
294
|
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
|
|
294
295
|
|
|
295
296
|
|
|
@@ -147,7 +147,10 @@ def _watch_and_verify(interval=0.5):
|
|
|
147
147
|
@click.option("--json", "as_json", is_flag=True, help="Emit JSON.")
|
|
148
148
|
def smoke(as_json):
|
|
149
149
|
"""Run a real-browser smoke check against a temporary development app."""
|
|
150
|
-
|
|
150
|
+
try:
|
|
151
|
+
report = run_smoke()
|
|
152
|
+
except RuntimeError as exc:
|
|
153
|
+
raise click.ClickException(str(exc)) from exc
|
|
151
154
|
if as_json:
|
|
152
155
|
click.echo(json.dumps(report, indent=2))
|
|
153
156
|
else:
|
|
@@ -167,7 +167,10 @@ def db_revision(message, autogenerate, head):
|
|
|
167
167
|
@db.command("check")
|
|
168
168
|
def db_check():
|
|
169
169
|
"""Fail if models have drifted away from the migrations."""
|
|
170
|
-
|
|
170
|
+
try:
|
|
171
|
+
drift = build_migration_drift_report(cli_app())
|
|
172
|
+
except RuntimeError as exc:
|
|
173
|
+
raise click.ClickException(str(exc)) from exc
|
|
171
174
|
if not drift:
|
|
172
175
|
console.print("[green]No migration drift: models match migrations[/]")
|
|
173
176
|
return
|
|
@@ -10,7 +10,7 @@ import inspect
|
|
|
10
10
|
from importlib.metadata import version
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
|
-
from quart.cli import QuartGroup
|
|
13
|
+
from quart.cli import NoAppException, QuartGroup, ScriptInfo
|
|
14
14
|
|
|
15
15
|
import stk.cli as cli
|
|
16
16
|
from stk.cli.base import load_app
|
|
@@ -44,7 +44,7 @@ class StkGroup(QuartGroup):
|
|
|
44
44
|
if command is not None and not command.hidden:
|
|
45
45
|
commands[name] = command
|
|
46
46
|
|
|
47
|
-
for title, names in SECTIONS:
|
|
47
|
+
for title, names in [*SECTIONS, *_app_sections(ctx)]:
|
|
48
48
|
rows = [
|
|
49
49
|
(name, commands.pop(name).get_short_help_str(limit=60))
|
|
50
50
|
for name in names
|
|
@@ -64,6 +64,17 @@ class StkGroup(QuartGroup):
|
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def _app_sections(ctx):
|
|
68
|
+
"""The app's own help sections, `STK_CLI_SECTIONS`; none outside an app."""
|
|
69
|
+
try:
|
|
70
|
+
app = ctx.ensure_object(ScriptInfo).load_app()
|
|
71
|
+
except NoAppException:
|
|
72
|
+
return []
|
|
73
|
+
return [
|
|
74
|
+
(title, list(names)) for title, names in app.config.get("STK_CLI_SECTIONS", ())
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
|
|
67
78
|
def _create_app(**_kwargs):
|
|
68
79
|
return load_app()
|
|
69
80
|
|
|
@@ -10,7 +10,7 @@ import time
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
|
-
import
|
|
13
|
+
import httpx2
|
|
14
14
|
|
|
15
15
|
from stk.agent_login import create_agent_login_token
|
|
16
16
|
from stk.cli.base import cli_app, run_async
|
|
@@ -255,10 +255,10 @@ def _wait_for_smoke_server(base_url, process):
|
|
|
255
255
|
f"{stdout.strip()}\n{stderr.strip()}"
|
|
256
256
|
)
|
|
257
257
|
try:
|
|
258
|
-
response =
|
|
258
|
+
response = httpx2.get(f"{base_url}/login", timeout=1)
|
|
259
259
|
if response.status_code < 500:
|
|
260
260
|
return
|
|
261
|
-
except
|
|
261
|
+
except httpx2.HTTPError:
|
|
262
262
|
pass
|
|
263
263
|
time.sleep(0.2)
|
|
264
264
|
raise click.ClickException("smoke server did not start within 20 seconds")
|
|
@@ -107,6 +107,7 @@ def throwaway_database_url(database_url: str, *, sync: bool = False):
|
|
|
107
107
|
SQLite cannot render. Postgres needs a role with CREATEDB.
|
|
108
108
|
"""
|
|
109
109
|
from sqlalchemy import create_engine, text
|
|
110
|
+
from sqlalchemy.exc import ProgrammingError
|
|
110
111
|
|
|
111
112
|
url = make_url(database_url)
|
|
112
113
|
if url.get_backend_name() != "postgresql":
|
|
@@ -119,8 +120,14 @@ def throwaway_database_url(database_url: str, *, sync: bool = False):
|
|
|
119
120
|
_render(url.set(database="postgres"), sync=True), isolation_level="AUTOCOMMIT"
|
|
120
121
|
)
|
|
121
122
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
try:
|
|
124
|
+
with admin.connect() as connection:
|
|
125
|
+
connection.execute(text(f'create database "{name}"'))
|
|
126
|
+
except ProgrammingError as exc:
|
|
127
|
+
raise RuntimeError(
|
|
128
|
+
f"role {url.username!r} cannot create a throwaway database: grant it"
|
|
129
|
+
" CREATEDB, or run this check with a role that has it"
|
|
130
|
+
) from exc
|
|
124
131
|
try:
|
|
125
132
|
yield _render(url.set(database=name), sync=sync)
|
|
126
133
|
finally:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import secrets
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import httpx2
|
|
5
5
|
from authlib.integrations.httpx_client import AsyncOAuth2Client
|
|
6
6
|
from authlib.oidc.core import CodeIDToken
|
|
7
7
|
from joserfc import jwt
|
|
@@ -180,7 +180,7 @@ def get_github_client():
|
|
|
180
180
|
|
|
181
181
|
|
|
182
182
|
async def google_discovery():
|
|
183
|
-
async with
|
|
183
|
+
async with httpx2.AsyncClient(timeout=10) as client:
|
|
184
184
|
response = await client.get(GOOGLE_DISCOVERY_URL)
|
|
185
185
|
response.raise_for_status()
|
|
186
186
|
return response.json()
|
|
@@ -265,7 +265,7 @@ async def google_callback():
|
|
|
265
265
|
redirect_uri=transaction["redirect_uri"],
|
|
266
266
|
code_verifier=transaction["verifier"],
|
|
267
267
|
)
|
|
268
|
-
async with
|
|
268
|
+
async with httpx2.AsyncClient(timeout=10) as client:
|
|
269
269
|
response = await client.get(discovery["jwks_uri"])
|
|
270
270
|
response.raise_for_status()
|
|
271
271
|
decoded = jwt.decode(
|
|
@@ -315,7 +315,7 @@ async def github_callback():
|
|
|
315
315
|
redirect_uri=transaction["redirect_uri"],
|
|
316
316
|
code_verifier=transaction["verifier"],
|
|
317
317
|
)
|
|
318
|
-
async with
|
|
318
|
+
async with httpx2.AsyncClient(timeout=10) as client:
|
|
319
319
|
headers = {
|
|
320
320
|
"Authorization": f"Bearer {token['access_token']}",
|
|
321
321
|
"Accept": "application/json",
|
|
@@ -79,6 +79,7 @@ op.create_index("ix_products_name", "products", ["name"])
|
|
|
79
79
|
from alembic import op
|
|
80
80
|
from sqlalchemy import text
|
|
81
81
|
|
|
82
|
+
|
|
82
83
|
def upgrade():
|
|
83
84
|
op.add_column("users", sa.Column("display_name", sa.String(255)))
|
|
84
85
|
op.execute(text("UPDATE users SET display_name = name WHERE display_name IS NULL"))
|
|
@@ -92,5 +93,7 @@ with op.batch_alter_table("users") as batch_op:
|
|
|
92
93
|
|
|
93
94
|
**Add a composite unique constraint:**
|
|
94
95
|
```python
|
|
95
|
-
op.create_unique_constraint(
|
|
96
|
+
op.create_unique_constraint(
|
|
97
|
+
"uq_oauth_provider_user", "oauth", ["provider", "provider_user_id"]
|
|
98
|
+
)
|
|
96
99
|
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Checks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
SECRET_KEY: ci-only-secret-key-not-for-production
|
|
9
|
+
SECURITY_PASSWORD_SALT: ci-only-password-salt-not-for-production
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
checks:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
- uses: astral-sh/setup-uv@v9.0.0
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: uv sync
|
|
19
|
+
- name: Install Chromium
|
|
20
|
+
run: uv run playwright install --with-deps chromium
|
|
21
|
+
- name: Migrate
|
|
22
|
+
run: uv run stk db upgrade
|
|
23
|
+
- name: Unit tests
|
|
24
|
+
run: uv run python -m unittest discover -s tests -v
|
|
25
|
+
- name: Verify (lint, sanity, migrations)
|
|
26
|
+
run: uv run stk verify --json
|
|
27
|
+
- name: Browser smoke (behavioral)
|
|
28
|
+
run: uv run stk smoke --json
|
|
29
|
+
- name: Upload smoke artifacts on failure
|
|
30
|
+
if: failure()
|
|
31
|
+
uses: actions/upload-artifact@v7
|
|
32
|
+
with:
|
|
33
|
+
name: smoke-artifacts
|
|
34
|
+
path: .stk/smoke/
|
|
35
|
+
if-no-files-found: ignore
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""An app may replace stk's landing page, and verify judges where / lands."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
from quart import redirect
|
|
7
|
+
|
|
8
|
+
from stk.app import Stk
|
|
9
|
+
from stk.checks import check_http_index
|
|
10
|
+
from stk.cli.main import _app_sections
|
|
11
|
+
from stk.settings import TestingConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AppIndexTest(unittest.IsolatedAsyncioTestCase):
|
|
15
|
+
async def test_index_can_be_replaced_and_a_redirect_passes_the_check(self):
|
|
16
|
+
app = Stk(__name__, config=TestingConfig)
|
|
17
|
+
|
|
18
|
+
async def home():
|
|
19
|
+
return redirect("/login")
|
|
20
|
+
|
|
21
|
+
app.view_functions["public.index"] = home
|
|
22
|
+
response = await app.test_client().get("/")
|
|
23
|
+
self.assertEqual(response.status_code, 302)
|
|
24
|
+
self.assertTrue(await check_http_index(app))
|
|
25
|
+
|
|
26
|
+
async def test_a_redirect_to_a_broken_page_fails_the_check(self):
|
|
27
|
+
app = Stk(__name__, config=TestingConfig)
|
|
28
|
+
|
|
29
|
+
async def home():
|
|
30
|
+
return redirect("/nowhere")
|
|
31
|
+
|
|
32
|
+
app.view_functions["public.index"] = home
|
|
33
|
+
self.assertFalse(await check_http_index(app))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AppHelpSectionsTest(unittest.TestCase):
|
|
37
|
+
def test_app_sections_come_from_config(self):
|
|
38
|
+
app = Stk(__name__, config=TestingConfig)
|
|
39
|
+
app.config["STK_CLI_SECTIONS"] = [("Runs", ("worker", "enrich"))]
|
|
40
|
+
ctx = SimpleNamespace(
|
|
41
|
+
ensure_object=lambda _: SimpleNamespace(load_app=lambda: app)
|
|
42
|
+
)
|
|
43
|
+
self.assertEqual(_app_sections(ctx), [("Runs", ["worker", "enrich"])])
|
|
@@ -385,7 +385,7 @@ class ProductionSecurityTests(unittest.IsolatedAsyncioTestCase):
|
|
|
385
385
|
"token_endpoint": "https://accounts.google.com/token",
|
|
386
386
|
"jwks_uri": "https://accounts.google.com/keys",
|
|
387
387
|
}
|
|
388
|
-
import
|
|
388
|
+
import httpx2
|
|
389
389
|
|
|
390
390
|
for invalid in (None, "nonce", "iss", "aud", "exp", "signature"):
|
|
391
391
|
with self.subTest(invalid=invalid):
|
|
@@ -429,8 +429,8 @@ class ProductionSecurityTests(unittest.IsolatedAsyncioTestCase):
|
|
|
429
429
|
"id_token": encoded,
|
|
430
430
|
"access_token": "fixture",
|
|
431
431
|
}
|
|
432
|
-
response =
|
|
433
|
-
200, request=
|
|
432
|
+
response = httpx2.Response(
|
|
433
|
+
200, request=httpx2.Request("GET", discovery["jwks_uri"]), json={}
|
|
434
434
|
)
|
|
435
435
|
response.json = lambda: {"keys": [RSAKey.import_key(public).as_dict()]}
|
|
436
436
|
http = AsyncMock()
|
|
@@ -442,7 +442,7 @@ class ProductionSecurityTests(unittest.IsolatedAsyncioTestCase):
|
|
|
442
442
|
new=AsyncMock(return_value=discovery),
|
|
443
443
|
),
|
|
444
444
|
patch("stk.public.views.get_google_client", return_value=oauth),
|
|
445
|
-
patch("stk.public.views.
|
|
445
|
+
patch("stk.public.views.httpx2.AsyncClient", return_value=http),
|
|
446
446
|
):
|
|
447
447
|
await client.get(
|
|
448
448
|
"/login/google/callback?code=fixture&state=" + args["state"][0]
|
|
@@ -281,7 +281,13 @@ class InitAndNewTests(unittest.TestCase):
|
|
|
281
281
|
self.assertIn('app = "acme.app:app"', pyproject)
|
|
282
282
|
self.assertIn(f'"stkit>={stk.__version__},<', pyproject)
|
|
283
283
|
self.assertNotIn("[tool.uv.sources]", pyproject)
|
|
284
|
-
for name in (
|
|
284
|
+
for name in (
|
|
285
|
+
".gitignore",
|
|
286
|
+
".env-sample",
|
|
287
|
+
"acme/settings.py",
|
|
288
|
+
"Dockerfile",
|
|
289
|
+
".github/workflows/checks.yml",
|
|
290
|
+
):
|
|
285
291
|
self.assertTrue((self.app / name).is_file(), name)
|
|
286
292
|
env = (self.app / ".env").read_text()
|
|
287
293
|
self.assertNotIn("\nSECRET_KEY=\n", env)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|