regstack 0.2.0__tar.gz → 0.2.1__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.
- regstack-0.2.1/.github/workflows/test.yml +147 -0
- {regstack-0.2.0 → regstack-0.2.1}/CHANGELOG.md +11 -0
- {regstack-0.2.0 → regstack-0.2.1}/PKG-INFO +1 -1
- {regstack-0.2.0 → regstack-0.2.1}/docs/changelog.md +30 -0
- {regstack-0.2.0 → regstack-0.2.1}/pyproject.toml +1 -1
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/repositories/mfa_code_repo.py +2 -14
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/repositories/pending_repo.py +2 -2
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/repositories/user_repo.py +2 -2
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/protocols.py +24 -6
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/repositories/mfa_code_repo.py +1 -4
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/models/_objectid.py +10 -2
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/account.py +1 -1
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/login.py +1 -1
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/phone.py +1 -1
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/register.py +1 -2
- regstack-0.2.1/src/regstack/version.py +1 -0
- regstack-0.2.1/tests/unit/test_base_install_imports.py +69 -0
- {regstack-0.2.0 → regstack-0.2.1}/uv.lock +1 -1
- regstack-0.2.0/.github/workflows/test.yml +0 -86
- regstack-0.2.0/src/regstack/version.py +0 -1
- {regstack-0.2.0 → regstack-0.2.1}/.github/workflows/publish.yml +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/.gitignore +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/.python-version +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/.readthedocs.yaml +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/CLAUDE.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/LICENSE +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/NOTICE +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/SECURITY.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/_static/.gitkeep +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/_templates/.gitkeep +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/api.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/architecture.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/cli.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/conf.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/configuration.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/embedding.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/index.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/quickstart.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/security.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/docs/theming.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/_common/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/_common/app.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/mongo/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/mongo/branding/theme.css +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/mongo/main.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/mongo/regstack.toml +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/postgres/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/postgres/main.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/postgres/regstack.toml +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/sqlite/README.md +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/sqlite/main.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/examples/sqlite/regstack.toml +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/regstack.toml.example +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/app.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/clock.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/dependencies.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/lockout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/password.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/base.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/factory.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/client.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/indexes.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/repositories/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/repositories/blacklist_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/mongo/repositories/login_attempt_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/migrations/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/migrations/env.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/migrations/script.py.mako +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/repositories/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/repositories/blacklist_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/repositories/login_attempt_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/repositories/pending_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/repositories/user_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/schema.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/backends/sql/types.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/__main__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/_runtime.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/admin.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/doctor.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/init.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/cli/migrate.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/config/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/config/loader.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/config/schema.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/config/secrets.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/base.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/composer.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/console.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/factory.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/ses.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/smtp.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/hooks/events.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/models/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/models/user.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/admin.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/logout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/password.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/routers/verify.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/sms/base.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/sms/factory.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/sms/null.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/sms/sns.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/pages.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/static/js/regstack.js +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tasks.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/conftest.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_account_management.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_admin_router.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_happy_path.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_indexes.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_mfa.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_sql_migrations.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/integration/test_verification.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/__init__.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_cli.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_jwt.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_lockout.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_mfa_code_repo.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_password.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_sms.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_smtp_backend.py +0 -0
- {regstack-0.2.0 → regstack-0.2.1}/tests/unit/test_ui_env.py +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: test-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
pytest:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.11", "3.12"]
|
|
20
|
+
services:
|
|
21
|
+
mongodb:
|
|
22
|
+
image: mongo:7
|
|
23
|
+
ports:
|
|
24
|
+
- 27017:27017
|
|
25
|
+
options: >-
|
|
26
|
+
--health-cmd "mongosh --quiet --eval 'db.runCommand({ping:1}).ok' --port 27017"
|
|
27
|
+
--health-interval 5s
|
|
28
|
+
--health-timeout 5s
|
|
29
|
+
--health-retries 12
|
|
30
|
+
--health-start-period 10s
|
|
31
|
+
postgres:
|
|
32
|
+
image: postgres:16
|
|
33
|
+
ports:
|
|
34
|
+
- 5432:5432
|
|
35
|
+
env:
|
|
36
|
+
POSTGRES_USER: regstack
|
|
37
|
+
POSTGRES_PASSWORD: regstack
|
|
38
|
+
POSTGRES_DB: regstack
|
|
39
|
+
options: >-
|
|
40
|
+
--health-cmd "pg_isready -U regstack"
|
|
41
|
+
--health-interval 5s
|
|
42
|
+
--health-timeout 5s
|
|
43
|
+
--health-retries 12
|
|
44
|
+
--health-start-period 10s
|
|
45
|
+
env:
|
|
46
|
+
# Drives the parametrized backend fixture in tests/conftest.py.
|
|
47
|
+
# Connect as superuser so the per-test fixture can CREATE/DROP DATABASE.
|
|
48
|
+
REGSTACK_TEST_POSTGRES_URL: postgresql+asyncpg://regstack:regstack@localhost:5432
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
|
|
52
|
+
- uses: astral-sh/setup-uv@v3
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: true
|
|
55
|
+
cache-dependency-glob: "uv.lock"
|
|
56
|
+
|
|
57
|
+
- name: Pin Python ${{ matrix.python-version }}
|
|
58
|
+
run: uv python pin ${{ matrix.python-version }}
|
|
59
|
+
|
|
60
|
+
- name: Sync dependencies
|
|
61
|
+
run: uv sync --extra dev
|
|
62
|
+
|
|
63
|
+
- name: ruff check
|
|
64
|
+
run: uv run ruff check src tests
|
|
65
|
+
|
|
66
|
+
- name: ruff format check
|
|
67
|
+
run: uv run ruff format --check src tests
|
|
68
|
+
|
|
69
|
+
- name: pytest (parallel, all backends)
|
|
70
|
+
run: uv run python -m pytest -n auto --tb=short
|
|
71
|
+
|
|
72
|
+
docs:
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- uses: astral-sh/setup-uv@v3
|
|
77
|
+
with:
|
|
78
|
+
enable-cache: true
|
|
79
|
+
cache-dependency-glob: "uv.lock"
|
|
80
|
+
- run: uv python pin 3.11
|
|
81
|
+
- run: uv sync --extra docs --extra dev
|
|
82
|
+
- run: uv run sphinx-build -b html -W --keep-going docs docs/_build/html
|
|
83
|
+
- uses: actions/upload-artifact@v4
|
|
84
|
+
with:
|
|
85
|
+
name: docs-html
|
|
86
|
+
path: docs/_build/html
|
|
87
|
+
|
|
88
|
+
base-install-smoketest:
|
|
89
|
+
# Catches the kind of regression that broke 0.2.0: an unconditional
|
|
90
|
+
# `from bson import ...` (or any other optional-extra import) at module
|
|
91
|
+
# top level makes `import regstack` fail for any user who didn't opt
|
|
92
|
+
# into the `mongo` extra. The unit test of the same name exercises the
|
|
93
|
+
# equivalent in-process via meta_path blocking; this job verifies the
|
|
94
|
+
# actual built wheel installs and imports against a clean Python.
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v4
|
|
98
|
+
- uses: astral-sh/setup-uv@v3
|
|
99
|
+
with:
|
|
100
|
+
enable-cache: true
|
|
101
|
+
cache-dependency-glob: "uv.lock"
|
|
102
|
+
- run: uv python pin 3.11
|
|
103
|
+
- name: Build wheel
|
|
104
|
+
run: uv build --wheel
|
|
105
|
+
- name: Install wheel into a clean venv (no extras)
|
|
106
|
+
run: |
|
|
107
|
+
python -m venv /tmp/smoke
|
|
108
|
+
/tmp/smoke/bin/pip install --upgrade pip
|
|
109
|
+
/tmp/smoke/bin/pip install dist/regstack-*.whl
|
|
110
|
+
- name: Import smoketest
|
|
111
|
+
run: |
|
|
112
|
+
/tmp/smoke/bin/python -c "
|
|
113
|
+
import regstack
|
|
114
|
+
from regstack import RegStack, RegStackConfig
|
|
115
|
+
assert RegStack.__module__ == 'regstack.app'
|
|
116
|
+
print('ok', regstack.__version__)
|
|
117
|
+
"
|
|
118
|
+
- name: SQLite end-to-end smoketest
|
|
119
|
+
run: |
|
|
120
|
+
/tmp/smoke/bin/python <<'PY'
|
|
121
|
+
import asyncio, secrets, tempfile
|
|
122
|
+
from pathlib import Path
|
|
123
|
+
from regstack import RegStack, RegStackConfig
|
|
124
|
+
from regstack.models.user import BaseUser
|
|
125
|
+
|
|
126
|
+
async def main() -> None:
|
|
127
|
+
with tempfile.TemporaryDirectory() as td:
|
|
128
|
+
cfg = RegStackConfig.load(
|
|
129
|
+
toml_path=Path("/dev/null"),
|
|
130
|
+
secrets_env_path=Path("/dev/null"),
|
|
131
|
+
jwt_secret=secrets.token_urlsafe(64),
|
|
132
|
+
database_url=f"sqlite+aiosqlite:///{td}/rs.db",
|
|
133
|
+
require_verification=False,
|
|
134
|
+
allow_registration=True,
|
|
135
|
+
rate_limit_disabled=True,
|
|
136
|
+
)
|
|
137
|
+
rs = RegStack(config=cfg)
|
|
138
|
+
await rs.install_schema()
|
|
139
|
+
user = await rs.users.create(
|
|
140
|
+
BaseUser(email="a@b.com", hashed_password="h", is_verified=True)
|
|
141
|
+
)
|
|
142
|
+
assert user.id
|
|
143
|
+
await rs.aclose()
|
|
144
|
+
print("sqlite ok")
|
|
145
|
+
|
|
146
|
+
asyncio.run(main())
|
|
147
|
+
PY
|
|
@@ -5,6 +5,17 @@ authoritative copy lives at
|
|
|
5
5
|
[`docs/changelog.md`](docs/changelog.md) and is rendered into the
|
|
6
6
|
Sphinx docs.
|
|
7
7
|
|
|
8
|
+
## 0.2.1 — 2026-04-28
|
|
9
|
+
|
|
10
|
+
Hotfix for 0.2.0: `import regstack` failed on a base install because
|
|
11
|
+
several modules in the import path (`models/_objectid.py`,
|
|
12
|
+
`backends/protocols.py`, four routers, and the SQL `mfa_code_repo`)
|
|
13
|
+
had unconditional `from bson …` / `from regstack.backends.mongo …`
|
|
14
|
+
imports — but `pymongo` became an optional `mongo` extra in 0.2.0.
|
|
15
|
+
Added a CI smoketest that builds the wheel and imports it in a
|
|
16
|
+
no-extras venv, plus an in-process regression test that blocks `bson`
|
|
17
|
+
/ `pymongo` via `sys.meta_path`.
|
|
18
|
+
|
|
8
19
|
## 0.2.0 — 2026-04-28
|
|
9
20
|
|
|
10
21
|
Multi-backend support — SQLite (default), Postgres, MongoDB — switched
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regstack
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Embeddable user registration, login, and account management for FastAPI apps. SQLite / Postgres / MongoDB.
|
|
5
5
|
Project-URL: Homepage, https://github.com/jdrumgoole/regstack
|
|
6
6
|
Project-URL: Repository, https://github.com/jdrumgoole/regstack
|
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Versions follow
|
|
4
4
|
[Semantic Versioning](https://semver.org/) once `1.0.0` ships.
|
|
5
5
|
|
|
6
|
+
## 0.2.1 — 2026-04-28
|
|
7
|
+
|
|
8
|
+
**Hotfix for 0.2.0.** `import regstack` was broken on any install that
|
|
9
|
+
didn't include the new `mongo` extra: `models/_objectid.py` imported
|
|
10
|
+
`bson` unconditionally, and four routers + the SQL MFA repo imported
|
|
11
|
+
shared error / enum types out of `regstack.backends.mongo.*`, which
|
|
12
|
+
in turn imports `pymongo` at module top level.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `models/_objectid.py` now imports `bson.ObjectId` lazily inside a
|
|
17
|
+
`try / except ImportError` and only uses it for `isinstance` checks
|
|
18
|
+
when present.
|
|
19
|
+
- `UserAlreadyExistsError`, `PendingAlreadyExistsError`,
|
|
20
|
+
`MfaVerifyOutcome`, and `MfaVerifyResult` moved from their backend
|
|
21
|
+
modules to `regstack.backends.protocols` (the backend-agnostic
|
|
22
|
+
location). Mongo modules re-export them for backwards compatibility.
|
|
23
|
+
- All consumer modules (`routers/register.py`, `routers/account.py`,
|
|
24
|
+
`routers/login.py`, `routers/phone.py`, the SQL MFA repo) updated to
|
|
25
|
+
import from `regstack.backends.protocols`.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- New `base-install-smoketest` CI job: builds the wheel and runs
|
|
30
|
+
`import regstack` + a SQLite end-to-end RegStack lifecycle in a
|
|
31
|
+
fresh venv with **no extras**. Will catch any future regression.
|
|
32
|
+
- New `tests/unit/test_base_install_imports.py` regression test that
|
|
33
|
+
uses `sys.meta_path` to block `bson` / `pymongo` and confirm
|
|
34
|
+
`import regstack` still succeeds.
|
|
35
|
+
|
|
6
36
|
## 0.2.0 — 2026-04-28
|
|
7
37
|
|
|
8
38
|
**Multi-backend support + Alembic migrations.** SQLite is now the
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass
|
|
4
3
|
from datetime import datetime
|
|
5
|
-
from enum import StrEnum
|
|
6
4
|
from typing import TYPE_CHECKING
|
|
7
5
|
|
|
8
6
|
from regstack.auth.tokens import hash_token
|
|
7
|
+
from regstack.backends.protocols import MfaVerifyOutcome, MfaVerifyResult
|
|
9
8
|
from regstack.models.mfa_code import MfaCode, MfaKind
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
@@ -14,18 +13,7 @@ if TYPE_CHECKING:
|
|
|
14
13
|
from regstack.auth.clock import Clock
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
OK = "ok"
|
|
19
|
-
WRONG = "wrong"
|
|
20
|
-
EXPIRED = "expired"
|
|
21
|
-
LOCKED = "locked"
|
|
22
|
-
MISSING = "missing"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass(slots=True, frozen=True)
|
|
26
|
-
class MfaVerifyResult:
|
|
27
|
-
outcome: MfaVerifyOutcome
|
|
28
|
-
attempts_remaining: int = 0
|
|
16
|
+
__all__ = ["MfaCodeRepo", "MfaVerifyOutcome", "MfaVerifyResult"]
|
|
29
17
|
|
|
30
18
|
|
|
31
19
|
class MfaCodeRepo:
|
|
@@ -6,14 +6,14 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
from bson import ObjectId
|
|
7
7
|
from pymongo.errors import DuplicateKeyError
|
|
8
8
|
|
|
9
|
+
from regstack.backends.protocols import PendingAlreadyExistsError
|
|
9
10
|
from regstack.models.pending_registration import PendingRegistration
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from pymongo.asynchronous.database import AsyncDatabase
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
"""A pending registration with this email already exists."""
|
|
16
|
+
__all__ = ["PendingAlreadyExistsError", "PendingRepo"]
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class PendingRepo:
|
|
@@ -7,14 +7,14 @@ from bson import ObjectId
|
|
|
7
7
|
from pymongo.errors import DuplicateKeyError
|
|
8
8
|
|
|
9
9
|
from regstack.auth.clock import Clock, SystemClock
|
|
10
|
+
from regstack.backends.protocols import UserAlreadyExistsError
|
|
10
11
|
from regstack.models.user import BaseUser
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from pymongo.asynchronous.database import AsyncDatabase
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
"""Raised when an attempt is made to insert a user with a duplicate email."""
|
|
17
|
+
__all__ = ["UserAlreadyExistsError", "UserRepo"]
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _bulk_revoke_cutoff(now: datetime) -> datetime:
|
|
@@ -11,7 +11,9 @@ or Mongo idiom feels "right" to the implementer.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
from dataclasses import dataclass
|
|
14
15
|
from datetime import datetime, timedelta
|
|
16
|
+
from enum import StrEnum
|
|
15
17
|
from typing import Protocol, runtime_checkable
|
|
16
18
|
|
|
17
19
|
from regstack.models.mfa_code import MfaCode, MfaKind
|
|
@@ -19,6 +21,20 @@ from regstack.models.pending_registration import PendingRegistration
|
|
|
19
21
|
from regstack.models.user import BaseUser
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
class MfaVerifyOutcome(StrEnum):
|
|
25
|
+
OK = "ok"
|
|
26
|
+
WRONG = "wrong"
|
|
27
|
+
EXPIRED = "expired"
|
|
28
|
+
LOCKED = "locked"
|
|
29
|
+
MISSING = "missing"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(slots=True, frozen=True)
|
|
33
|
+
class MfaVerifyResult:
|
|
34
|
+
outcome: MfaVerifyOutcome
|
|
35
|
+
attempts_remaining: int = 0
|
|
36
|
+
|
|
37
|
+
|
|
22
38
|
class UserAlreadyExistsError(Exception):
|
|
23
39
|
"""Raised when an attempt is made to insert a user with a duplicate email,
|
|
24
40
|
or to set an email that another user already owns.
|
|
@@ -28,6 +44,14 @@ class UserAlreadyExistsError(Exception):
|
|
|
28
44
|
"""
|
|
29
45
|
|
|
30
46
|
|
|
47
|
+
class PendingAlreadyExistsError(Exception):
|
|
48
|
+
"""A pending registration with this email already exists.
|
|
49
|
+
|
|
50
|
+
Backend-agnostic: each repo raises this on its own duplicate path so
|
|
51
|
+
callers can branch on the type without importing a backend module.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
31
55
|
@runtime_checkable
|
|
32
56
|
class UserRepoProtocol(Protocol):
|
|
33
57
|
async def create(self, user: BaseUser) -> BaseUser: ...
|
|
@@ -155,9 +179,3 @@ class MfaCodeRepoProtocol(Protocol):
|
|
|
155
179
|
async def find(self, *, user_id: str, kind: MfaKind) -> MfaCode | None: ...
|
|
156
180
|
|
|
157
181
|
async def purge_expired(self, now: datetime | None = None) -> int: ...
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# Re-exported here so callers don't need a deeper import. Lives in the
|
|
161
|
-
# Mongo backend module because it predates the SQL backend; the dataclass
|
|
162
|
-
# is backend-agnostic, only the import path is historical.
|
|
163
|
-
from regstack.backends.mongo.repositories.mfa_code_repo import MfaVerifyResult # noqa: E402
|
|
@@ -6,10 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from sqlalchemy import and_, delete, select, update
|
|
7
7
|
|
|
8
8
|
from regstack.auth.tokens import hash_token
|
|
9
|
-
from regstack.backends.
|
|
10
|
-
MfaVerifyOutcome,
|
|
11
|
-
MfaVerifyResult,
|
|
12
|
-
)
|
|
9
|
+
from regstack.backends.protocols import MfaVerifyOutcome, MfaVerifyResult
|
|
13
10
|
from regstack.backends.sql.schema import mfa_codes_table
|
|
14
11
|
from regstack.models.mfa_code import MfaCode, MfaKind
|
|
15
12
|
|
|
@@ -17,10 +17,18 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from typing import Annotated, Any
|
|
19
19
|
|
|
20
|
-
from bson import ObjectId
|
|
21
20
|
from pydantic import GetCoreSchemaHandler
|
|
22
21
|
from pydantic_core import CoreSchema, core_schema
|
|
23
22
|
|
|
23
|
+
# bson is imported lazily so the package remains importable on a base
|
|
24
|
+
# install (the `mongo` extra is what pulls pymongo, and pymongo is the
|
|
25
|
+
# only consumer of bson). When the Mongo backend is in use, bson is
|
|
26
|
+
# always present.
|
|
27
|
+
try:
|
|
28
|
+
from bson import ObjectId as _BsonObjectId # type: ignore[import-not-found]
|
|
29
|
+
except ImportError: # pragma: no cover — exercised only without the mongo extra
|
|
30
|
+
_BsonObjectId = None # type: ignore[assignment,misc]
|
|
31
|
+
|
|
24
32
|
|
|
25
33
|
class _IdValidator:
|
|
26
34
|
@classmethod
|
|
@@ -28,7 +36,7 @@ class _IdValidator:
|
|
|
28
36
|
cls, source_type: Any, handler: GetCoreSchemaHandler
|
|
29
37
|
) -> CoreSchema:
|
|
30
38
|
def validate(value: Any) -> str:
|
|
31
|
-
if isinstance(value,
|
|
39
|
+
if _BsonObjectId is not None and isinstance(value, _BsonObjectId):
|
|
32
40
|
return str(value)
|
|
33
41
|
if isinstance(value, str) and value:
|
|
34
42
|
return value
|
|
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, status
|
|
|
7
7
|
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
|
8
8
|
|
|
9
9
|
from regstack.auth.jwt import TokenError
|
|
10
|
-
from regstack.backends.
|
|
10
|
+
from regstack.backends.protocols import UserAlreadyExistsError
|
|
11
11
|
from regstack.config.secrets import derive_secret
|
|
12
12
|
from regstack.models.user import BaseUser, UserPublic
|
|
13
13
|
from regstack.routers._schemas import MessageResponse, PasswordStr
|
|
@@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
9
9
|
|
|
10
10
|
from regstack.auth.jwt import TokenError
|
|
11
11
|
from regstack.auth.mfa import generate_mfa_code
|
|
12
|
-
from regstack.backends.
|
|
12
|
+
from regstack.backends.protocols import MfaVerifyOutcome
|
|
13
13
|
from regstack.models.mfa_code import MfaCode
|
|
14
14
|
from regstack.routers._schemas import LoginRequest, TokenResponse
|
|
15
15
|
from regstack.sms.base import SmsMessage
|
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
8
8
|
|
|
9
9
|
from regstack.auth.jwt import TokenError
|
|
10
10
|
from regstack.auth.mfa import generate_mfa_code
|
|
11
|
-
from regstack.backends.
|
|
11
|
+
from regstack.backends.protocols import MfaVerifyOutcome
|
|
12
12
|
from regstack.models.mfa_code import MfaCode
|
|
13
13
|
from regstack.models.user import BaseUser, UserPublic
|
|
14
14
|
from regstack.routers._schemas import MessageResponse
|
|
@@ -6,8 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from fastapi import APIRouter, HTTPException, status
|
|
7
7
|
|
|
8
8
|
from regstack.auth.tokens import generate_verification_token
|
|
9
|
-
from regstack.backends.
|
|
10
|
-
from regstack.backends.mongo.repositories.user_repo import UserAlreadyExistsError
|
|
9
|
+
from regstack.backends.protocols import PendingAlreadyExistsError, UserAlreadyExistsError
|
|
11
10
|
from regstack.models.pending_registration import PendingRegistration
|
|
12
11
|
from regstack.models.user import BaseUser, UserCreate, UserPublic
|
|
13
12
|
from regstack.routers._schemas import PendingResponse
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Regression test: ``import regstack`` must not require optional extras.
|
|
2
|
+
|
|
3
|
+
regstack 0.2.0 shipped to PyPI broken because several modules in the
|
|
4
|
+
hot import path (`routers/account.py`, `routers/login.py`,
|
|
5
|
+
`models/_objectid.py`, …) had unconditional ``from bson …`` /
|
|
6
|
+
``from pymongo …`` / ``from regstack.backends.mongo …`` statements.
|
|
7
|
+
On a base install (no ``mongo`` extra) ``import regstack`` raised
|
|
8
|
+
``ModuleNotFoundError: No module named 'bson'``.
|
|
9
|
+
|
|
10
|
+
This test runs ``import regstack`` in a subprocess with ``bson`` and
|
|
11
|
+
``pymongo`` blocked, so a future regression is caught even when the
|
|
12
|
+
test environment happens to have pymongo installed for other reasons.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import textwrap
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_import_regstack_without_pymongo() -> None:
|
|
23
|
+
"""``import regstack`` and ``from regstack import RegStack, RegStackConfig``
|
|
24
|
+
must work on a base install — no optional extras.
|
|
25
|
+
|
|
26
|
+
We can't actually uninstall pymongo from the test venv, so we
|
|
27
|
+
simulate "not installed" by inserting a finder that raises
|
|
28
|
+
ImportError for ``bson`` and ``pymongo`` at the top of sys.meta_path.
|
|
29
|
+
"""
|
|
30
|
+
program = textwrap.dedent(
|
|
31
|
+
"""
|
|
32
|
+
import importlib.abc, importlib.machinery, sys
|
|
33
|
+
|
|
34
|
+
BLOCKED = {"bson", "pymongo"}
|
|
35
|
+
|
|
36
|
+
class BlockMongo(importlib.abc.MetaPathFinder):
|
|
37
|
+
def find_spec(self, name, path, target=None):
|
|
38
|
+
root = name.split(".", 1)[0]
|
|
39
|
+
if root in BLOCKED:
|
|
40
|
+
raise ModuleNotFoundError(f"No module named {name!r}")
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
sys.meta_path.insert(0, BlockMongo())
|
|
44
|
+
# Drop anything pymongo-touching that may have been pre-imported
|
|
45
|
+
# by pytest's collection of sibling test modules.
|
|
46
|
+
for mod in list(sys.modules):
|
|
47
|
+
root = mod.split(".", 1)[0]
|
|
48
|
+
if root in BLOCKED or mod.startswith("regstack"):
|
|
49
|
+
del sys.modules[mod]
|
|
50
|
+
|
|
51
|
+
import regstack
|
|
52
|
+
from regstack import RegStack, RegStackConfig
|
|
53
|
+
|
|
54
|
+
assert RegStack.__module__ == "regstack.app"
|
|
55
|
+
assert RegStackConfig.__module__ == "regstack.config.schema"
|
|
56
|
+
print("ok", regstack.__version__)
|
|
57
|
+
"""
|
|
58
|
+
)
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
[sys.executable, "-c", program],
|
|
61
|
+
capture_output=True,
|
|
62
|
+
text=True,
|
|
63
|
+
timeout=30,
|
|
64
|
+
)
|
|
65
|
+
assert result.returncode == 0, (
|
|
66
|
+
f"import regstack failed without pymongo:\nstdout={result.stdout!r}"
|
|
67
|
+
f"\nstderr={result.stderr!r}"
|
|
68
|
+
)
|
|
69
|
+
assert result.stdout.startswith("ok "), result.stdout
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
name: test
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
concurrency:
|
|
10
|
-
group: test-${{ github.ref }}
|
|
11
|
-
cancel-in-progress: true
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
pytest:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
strategy:
|
|
17
|
-
fail-fast: false
|
|
18
|
-
matrix:
|
|
19
|
-
python-version: ["3.11", "3.12"]
|
|
20
|
-
services:
|
|
21
|
-
mongodb:
|
|
22
|
-
image: mongo:7
|
|
23
|
-
ports:
|
|
24
|
-
- 27017:27017
|
|
25
|
-
options: >-
|
|
26
|
-
--health-cmd "mongosh --quiet --eval 'db.runCommand({ping:1}).ok' --port 27017"
|
|
27
|
-
--health-interval 5s
|
|
28
|
-
--health-timeout 5s
|
|
29
|
-
--health-retries 12
|
|
30
|
-
--health-start-period 10s
|
|
31
|
-
postgres:
|
|
32
|
-
image: postgres:16
|
|
33
|
-
ports:
|
|
34
|
-
- 5432:5432
|
|
35
|
-
env:
|
|
36
|
-
POSTGRES_USER: regstack
|
|
37
|
-
POSTGRES_PASSWORD: regstack
|
|
38
|
-
POSTGRES_DB: regstack
|
|
39
|
-
options: >-
|
|
40
|
-
--health-cmd "pg_isready -U regstack"
|
|
41
|
-
--health-interval 5s
|
|
42
|
-
--health-timeout 5s
|
|
43
|
-
--health-retries 12
|
|
44
|
-
--health-start-period 10s
|
|
45
|
-
env:
|
|
46
|
-
# Drives the parametrized backend fixture in tests/conftest.py.
|
|
47
|
-
# Connect as superuser so the per-test fixture can CREATE/DROP DATABASE.
|
|
48
|
-
REGSTACK_TEST_POSTGRES_URL: postgresql+asyncpg://regstack:regstack@localhost:5432
|
|
49
|
-
steps:
|
|
50
|
-
- uses: actions/checkout@v4
|
|
51
|
-
|
|
52
|
-
- uses: astral-sh/setup-uv@v3
|
|
53
|
-
with:
|
|
54
|
-
enable-cache: true
|
|
55
|
-
cache-dependency-glob: "uv.lock"
|
|
56
|
-
|
|
57
|
-
- name: Pin Python ${{ matrix.python-version }}
|
|
58
|
-
run: uv python pin ${{ matrix.python-version }}
|
|
59
|
-
|
|
60
|
-
- name: Sync dependencies
|
|
61
|
-
run: uv sync --extra dev
|
|
62
|
-
|
|
63
|
-
- name: ruff check
|
|
64
|
-
run: uv run ruff check src tests
|
|
65
|
-
|
|
66
|
-
- name: ruff format check
|
|
67
|
-
run: uv run ruff format --check src tests
|
|
68
|
-
|
|
69
|
-
- name: pytest (parallel, all backends)
|
|
70
|
-
run: uv run python -m pytest -n auto --tb=short
|
|
71
|
-
|
|
72
|
-
docs:
|
|
73
|
-
runs-on: ubuntu-latest
|
|
74
|
-
steps:
|
|
75
|
-
- uses: actions/checkout@v4
|
|
76
|
-
- uses: astral-sh/setup-uv@v3
|
|
77
|
-
with:
|
|
78
|
-
enable-cache: true
|
|
79
|
-
cache-dependency-glob: "uv.lock"
|
|
80
|
-
- run: uv python pin 3.11
|
|
81
|
-
- run: uv sync --extra docs --extra dev
|
|
82
|
-
- run: uv run sphinx-build -b html -W --keep-going docs docs/_build/html
|
|
83
|
-
- uses: actions/upload-artifact@v4
|
|
84
|
-
with:
|
|
85
|
-
name: docs-html
|
|
86
|
-
path: docs/_build/html
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
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
|