regstack 0.1.1__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.1.1 → regstack-0.2.1}/.github/workflows/publish.yml +4 -1
- regstack-0.2.1/.github/workflows/test.yml +147 -0
- regstack-0.2.1/CHANGELOG.md +42 -0
- {regstack-0.1.1 → regstack-0.2.1}/CLAUDE.md +24 -0
- regstack-0.2.1/PKG-INFO +272 -0
- regstack-0.2.1/README.md +208 -0
- regstack-0.2.1/docs/architecture.md +210 -0
- regstack-0.2.1/docs/changelog.md +179 -0
- {regstack-0.1.1 → regstack-0.2.1}/docs/cli.md +5 -4
- {regstack-0.1.1 → regstack-0.2.1}/docs/configuration.md +36 -5
- regstack-0.2.1/docs/embedding.md +200 -0
- regstack-0.2.1/docs/index.md +99 -0
- regstack-0.2.1/docs/quickstart.md +157 -0
- regstack-0.2.1/docs/security.md +226 -0
- regstack-0.2.1/examples/_common/app.py +101 -0
- regstack-0.2.1/examples/mongo/main.py +31 -0
- {regstack-0.1.1/examples/minimal → regstack-0.2.1/examples/mongo}/regstack.toml +1 -0
- regstack-0.2.1/examples/postgres/README.md +29 -0
- regstack-0.2.1/examples/postgres/main.py +30 -0
- regstack-0.2.1/examples/postgres/regstack.toml +29 -0
- regstack-0.2.1/examples/sqlite/README.md +33 -0
- regstack-0.2.1/examples/sqlite/main.py +34 -0
- regstack-0.2.1/examples/sqlite/regstack.toml +30 -0
- {regstack-0.1.1 → regstack-0.2.1}/pyproject.toml +16 -3
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/app.py +31 -15
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/dependencies.py +2 -2
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/lockout.py +1 -1
- regstack-0.2.1/src/regstack/backends/__init__.py +21 -0
- regstack-0.2.1/src/regstack/backends/base.py +73 -0
- regstack-0.2.1/src/regstack/backends/factory.py +50 -0
- regstack-0.2.1/src/regstack/backends/mongo/__init__.py +5 -0
- regstack-0.2.1/src/regstack/backends/mongo/backend.py +57 -0
- regstack-0.2.1/src/regstack/backends/mongo/client.py +38 -0
- regstack-0.2.1/src/regstack/backends/mongo/repositories/__init__.py +27 -0
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/mongo}/repositories/login_attempt_repo.py +6 -0
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/mongo}/repositories/mfa_code_repo.py +16 -16
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/mongo}/repositories/pending_repo.py +2 -2
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/mongo}/repositories/user_repo.py +20 -6
- regstack-0.2.1/src/regstack/backends/protocols.py +181 -0
- regstack-0.2.1/src/regstack/backends/sql/__init__.py +3 -0
- regstack-0.2.1/src/regstack/backends/sql/backend.py +73 -0
- regstack-0.2.1/src/regstack/backends/sql/migrations/__init__.py +124 -0
- regstack-0.2.1/src/regstack/backends/sql/migrations/env.py +69 -0
- regstack-0.2.1/src/regstack/backends/sql/migrations/script.py.mako +28 -0
- regstack-0.2.1/src/regstack/backends/sql/migrations/versions/0001_initial_schema.py +108 -0
- regstack-0.2.1/src/regstack/backends/sql/repositories/blacklist_repo.py +36 -0
- regstack-0.2.1/src/regstack/backends/sql/repositories/login_attempt_repo.py +45 -0
- regstack-0.2.1/src/regstack/backends/sql/repositories/mfa_code_repo.py +115 -0
- regstack-0.2.1/src/regstack/backends/sql/repositories/pending_repo.py +69 -0
- regstack-0.2.1/src/regstack/backends/sql/repositories/user_repo.py +198 -0
- regstack-0.2.1/src/regstack/backends/sql/schema.py +139 -0
- regstack-0.2.1/src/regstack/backends/sql/types.py +37 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/cli/__main__.py +3 -1
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/cli/_runtime.py +7 -9
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/cli/doctor.py +57 -33
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/cli/init.py +46 -8
- regstack-0.2.1/src/regstack/cli/migrate.py +64 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/config/schema.py +7 -1
- regstack-0.2.1/src/regstack/models/_objectid.py +55 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/account.py +1 -1
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/admin.py +10 -4
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/login.py +1 -1
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/phone.py +1 -1
- {regstack-0.1.1 → 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/tasks.py +347 -0
- regstack-0.2.1/tests/conftest.py +266 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_indexes.py +14 -0
- regstack-0.2.1/tests/integration/test_sql_migrations.py +89 -0
- regstack-0.2.1/tests/unit/__init__.py +0 -0
- regstack-0.2.1/tests/unit/test_base_install_imports.py +69 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_cli.py +13 -12
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_lockout.py +20 -4
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_mfa_code_repo.py +17 -4
- {regstack-0.1.1 → regstack-0.2.1}/uv.lock +219 -5
- regstack-0.1.1/.github/workflows/test.yml +0 -71
- regstack-0.1.1/CHANGELOG.md +0 -19
- regstack-0.1.1/PKG-INFO +0 -209
- regstack-0.1.1/README.md +0 -153
- regstack-0.1.1/docs/architecture.md +0 -134
- regstack-0.1.1/docs/changelog.md +0 -82
- regstack-0.1.1/docs/embedding.md +0 -146
- regstack-0.1.1/docs/index.md +0 -55
- regstack-0.1.1/docs/quickstart.md +0 -115
- regstack-0.1.1/docs/security.md +0 -170
- regstack-0.1.1/examples/minimal/main.py +0 -109
- regstack-0.1.1/src/regstack/db/__init__.py +0 -17
- regstack-0.1.1/src/regstack/db/client.py +0 -26
- regstack-0.1.1/src/regstack/models/_objectid.py +0 -30
- regstack-0.1.1/src/regstack/version.py +0 -1
- regstack-0.1.1/tasks.py +0 -98
- regstack-0.1.1/tests/conftest.py +0 -144
- {regstack-0.1.1 → regstack-0.2.1}/.gitignore +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/.python-version +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/.readthedocs.yaml +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/LICENSE +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/NOTICE +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/SECURITY.md +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/docs/_static/.gitkeep +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/docs/_templates/.gitkeep +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/docs/api.md +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/docs/conf.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/docs/theming.md +0 -0
- {regstack-0.1.1/src/regstack/cli → regstack-0.2.1/examples/_common}/__init__.py +0 -0
- {regstack-0.1.1/examples/minimal → regstack-0.2.1/examples/mongo}/README.md +0 -0
- {regstack-0.1.1/examples/minimal → regstack-0.2.1/examples/mongo}/branding/theme.css +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/regstack.toml.example +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/clock.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/jwt.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/mfa.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/password.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/auth/tokens.py +0 -0
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/mongo}/indexes.py +0 -0
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/mongo}/repositories/blacklist_repo.py +0 -0
- {regstack-0.1.1/src/regstack/db → regstack-0.2.1/src/regstack/backends/sql}/repositories/__init__.py +0 -0
- {regstack-0.1.1/tests → regstack-0.2.1/src/regstack/cli}/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/cli/admin.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/config/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/config/loader.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/config/secrets.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/base.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/composer.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/console.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/factory.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/ses.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/smtp.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/email_change.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/email_change.subject.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/email_change.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/password_reset.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/password_reset.subject.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/password_reset.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/sms_login_mfa.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/sms_phone_setup.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/verification.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/verification.subject.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/email/templates/verification.txt +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/hooks/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/hooks/events.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/models/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/models/login_attempt.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/models/mfa_code.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/models/pending_registration.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/models/user.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/_schemas.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/logout.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/password.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/routers/verify.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/sms/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/sms/base.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/sms/factory.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/sms/null.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/sms/sns.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/sms/twilio.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/pages.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/static/css/core.css +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/static/css/theme.css +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/static/js/regstack.js +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/email_change_confirm.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/forgot.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/login.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/me.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/mfa_confirm.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/register.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/reset.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/auth/verify.html +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/src/regstack/ui/templates/base.html +0 -0
- {regstack-0.1.1/tests/integration → regstack-0.2.1/tests}/__init__.py +0 -0
- {regstack-0.1.1/tests/unit → regstack-0.2.1/tests/integration}/__init__.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_account_management.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_admin_router.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_happy_path.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_login_lockout.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_mfa.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_password_reset.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_ui_router.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/integration/test_verification.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_config_loader.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_jwt.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_mail_composer.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_password.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_ses_backend.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_sms.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_smtp_backend.py +0 -0
- {regstack-0.1.1 → regstack-0.2.1}/tests/unit/test_ui_env.py +0 -0
|
@@ -30,7 +30,10 @@ jobs:
|
|
|
30
30
|
run: |
|
|
31
31
|
tag="${GITHUB_REF##*/}" # e.g. v0.1.0
|
|
32
32
|
tag_version="${tag#v}" # e.g. 0.1.0
|
|
33
|
-
|
|
33
|
+
# Read version from pyproject.toml directly — importing the package
|
|
34
|
+
# would pull in optional-extra dependencies (e.g. pymongo) that
|
|
35
|
+
# aren't installed in this build job.
|
|
36
|
+
pkg_version=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
|
|
34
37
|
if [ "$tag_version" != "$pkg_version" ]; then
|
|
35
38
|
echo "Tag $tag does not match package version $pkg_version" >&2
|
|
36
39
|
exit 1
|
|
@@ -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
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The
|
|
4
|
+
authoritative copy lives at
|
|
5
|
+
[`docs/changelog.md`](docs/changelog.md) and is rendered into the
|
|
6
|
+
Sphinx docs.
|
|
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
|
+
|
|
19
|
+
## 0.2.0 — 2026-04-28
|
|
20
|
+
|
|
21
|
+
Multi-backend support — SQLite (default), Postgres, MongoDB — switched
|
|
22
|
+
by `database_url` URL scheme. Bundled Alembic migrations for SQL
|
|
23
|
+
backends. Embedding API change: `RegStack(config=, db=)` →
|
|
24
|
+
`RegStack(config=, backend=None)`. README + core docs rewritten for
|
|
25
|
+
less-expert readers (problem framing, hyperlinks to external
|
|
26
|
+
standards, comparison vs Auth0/Clerk/Keycloak/fastapi-users).
|
|
27
|
+
|
|
28
|
+
See [`docs/changelog.md`](docs/changelog.md) for the full per-feature
|
|
29
|
+
breakdown.
|
|
30
|
+
|
|
31
|
+
## 0.1.1 — 2026-04-27
|
|
32
|
+
|
|
33
|
+
- Rewrite README relative links as absolute URLs so they resolve on the
|
|
34
|
+
PyPI project page. README-only release.
|
|
35
|
+
|
|
36
|
+
## 0.1.0 — 2026-04-27
|
|
37
|
+
|
|
38
|
+
First tagged release. Bundles M1–M6 from the development plan into a
|
|
39
|
+
single Apache-2.0 package on PyPI.
|
|
40
|
+
|
|
41
|
+
See [`docs/changelog.md`](docs/changelog.md) for the per-milestone
|
|
42
|
+
breakdown of M1 through M6.
|
|
@@ -287,6 +287,30 @@ collections.** The full suite must pass under `pytest -n auto` reliably —
|
|
|
287
287
|
flaky tests are bugs, not noise. Time-dependent assertions use the
|
|
288
288
|
`frozen_clock` fixture, never `time.sleep` or wall-clock delays.
|
|
289
289
|
|
|
290
|
+
### A test run that doesn't cover every backend is a failing test
|
|
291
|
+
|
|
292
|
+
The whole point of the SQL + Mongo abstraction is parity. **Reporting
|
|
293
|
+
"all tests passed" when the run only exercised one or two backends is a
|
|
294
|
+
lie.** Treat any partial-backend run as red until it's been re-run
|
|
295
|
+
across the full matrix.
|
|
296
|
+
|
|
297
|
+
Concretely:
|
|
298
|
+
|
|
299
|
+
- `inv test-sqlite` is fine for tight inner-loop iteration, but it is
|
|
300
|
+
**not** a release gate.
|
|
301
|
+
- Before declaring a feature done — and definitely before merging /
|
|
302
|
+
tagging — run `inv test-all`, which exercises sqlite + mongo +
|
|
303
|
+
postgres in one go. Use `inv db-up` first to bring the local services
|
|
304
|
+
up if they aren't already (`inv db-status` reports what's running).
|
|
305
|
+
- The CI matrix runs all three backends on every push. A green local
|
|
306
|
+
`inv test-sqlite` followed by a CI failure on `mongo` or `postgres`
|
|
307
|
+
is the failure pattern this rule exists to prevent.
|
|
308
|
+
- A test that's `mongo`-only or `sqlite`-only by design must
|
|
309
|
+
short-circuit the parametrized fixture (see how
|
|
310
|
+
`tests/integration/test_indexes.py` does it with a local
|
|
311
|
+
`backend_kind = "mongo"` fixture override). Skipping silently because
|
|
312
|
+
a service isn't running doesn't count.
|
|
313
|
+
|
|
290
314
|
## Conventions
|
|
291
315
|
|
|
292
316
|
- **Python 3.11+.** Use `from __future__ import annotations` in every module.
|
regstack-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: regstack
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Embeddable user registration, login, and account management for FastAPI apps. SQLite / Postgres / MongoDB.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jdrumgoole/regstack
|
|
6
|
+
Project-URL: Repository, https://github.com/jdrumgoole/regstack
|
|
7
|
+
Author-email: Joe Drumgoole <joe@joedrumgoole.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Keywords: auth,fastapi,mongodb,registration,users
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: aiosmtplib>=3.0
|
|
20
|
+
Requires-Dist: aiosqlite>=0.20
|
|
21
|
+
Requires-Dist: alembic>=1.13
|
|
22
|
+
Requires-Dist: click>=8.1
|
|
23
|
+
Requires-Dist: dnspython>=2.6
|
|
24
|
+
Requires-Dist: email-validator>=2.1
|
|
25
|
+
Requires-Dist: fastapi>=0.110
|
|
26
|
+
Requires-Dist: jinja2>=3.1
|
|
27
|
+
Requires-Dist: pwdlib[argon2]>=0.2.1
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.2
|
|
29
|
+
Requires-Dist: pydantic>=2.6
|
|
30
|
+
Requires-Dist: pyjwt>=2.8
|
|
31
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
32
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: anyio>=4.3; extra == 'dev'
|
|
35
|
+
Requires-Dist: asyncpg>=0.29; extra == 'dev'
|
|
36
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
37
|
+
Requires-Dist: invoke>=2.2; extra == 'dev'
|
|
38
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
39
|
+
Requires-Dist: pymongo>=4.9; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
45
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
|
|
46
|
+
Provides-Extra: docs
|
|
47
|
+
Requires-Dist: furo>=2024.1; extra == 'docs'
|
|
48
|
+
Requires-Dist: myst-parser>=3.0; extra == 'docs'
|
|
49
|
+
Requires-Dist: sphinx-autobuild>=2024.4; extra == 'docs'
|
|
50
|
+
Requires-Dist: sphinx-autodoc-typehints>=2.0; extra == 'docs'
|
|
51
|
+
Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
|
|
52
|
+
Requires-Dist: sphinx>=7.3; extra == 'docs'
|
|
53
|
+
Provides-Extra: mongo
|
|
54
|
+
Requires-Dist: pymongo>=4.9; extra == 'mongo'
|
|
55
|
+
Provides-Extra: postgres
|
|
56
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
57
|
+
Provides-Extra: ses
|
|
58
|
+
Requires-Dist: aioboto3>=12.3; extra == 'ses'
|
|
59
|
+
Provides-Extra: sns
|
|
60
|
+
Requires-Dist: aioboto3>=12.3; extra == 'sns'
|
|
61
|
+
Provides-Extra: twilio
|
|
62
|
+
Requires-Dist: twilio>=9.0; extra == 'twilio'
|
|
63
|
+
Description-Content-Type: text/markdown
|
|
64
|
+
|
|
65
|
+
# regstack
|
|
66
|
+
|
|
67
|
+
[](https://github.com/jdrumgoole/regstack/actions/workflows/test.yml)
|
|
68
|
+
[](https://www.python.org/)
|
|
69
|
+
[](https://fastapi.tiangolo.com/)
|
|
70
|
+
[](https://github.com/jdrumgoole/regstack/blob/main/LICENSE)
|
|
71
|
+
|
|
72
|
+
**Production-grade user accounts for your [FastAPI](https://fastapi.tiangolo.com/)
|
|
73
|
+
app — without the vendor lock-in, the second service to run, or the homegrown
|
|
74
|
+
auth bugs.**
|
|
75
|
+
|
|
76
|
+
`pip install regstack`, point it at SQLite (default), [PostgreSQL](https://www.postgresql.org/),
|
|
77
|
+
or [MongoDB](https://www.mongodb.com/), and you have register / login /
|
|
78
|
+
verify-email / reset-password / change-email / delete-account / optional SMS
|
|
79
|
+
two-factor / admin endpoints / themable HTML pages — all behind a small Python
|
|
80
|
+
API and one config file.
|
|
81
|
+
|
|
82
|
+
📚 **Docs:** <https://regstack.readthedocs.io>
|
|
83
|
+
·
|
|
84
|
+
🧪 **Try it:** [`examples/sqlite`](https://github.com/jdrumgoole/regstack/tree/main/examples/sqlite)
|
|
85
|
+
·
|
|
86
|
+
🛡️ **Security model:** [security guide](https://regstack.readthedocs.io/en/latest/security.html)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## The problem regstack solves
|
|
91
|
+
|
|
92
|
+
Every web application that has users eventually needs the same dozen
|
|
93
|
+
endpoints: register, log in, log out, verify email, reset a forgotten
|
|
94
|
+
password, change password, change email, delete account, list users for
|
|
95
|
+
the admin panel, lock out brute-force attackers, and ideally a second
|
|
96
|
+
factor. Every one of those endpoints has a well-known way to get
|
|
97
|
+
subtly wrong:
|
|
98
|
+
|
|
99
|
+
- **Password hashing.** Use [Argon2](https://en.wikipedia.org/wiki/Argon2)
|
|
100
|
+
(the [PHC winner](https://www.password-hashing.net/)), not MD5, SHA-1,
|
|
101
|
+
bcrypt-without-pepper, or — somehow still common — plain text.
|
|
102
|
+
- **Token revocation.** A [JWT](https://datatracker.ietf.org/doc/html/rfc7519)
|
|
103
|
+
is signed and self-contained: the server can't "log it out" unless you
|
|
104
|
+
build a revocation list. Forget this and a stolen token works until it
|
|
105
|
+
expires.
|
|
106
|
+
- **Account enumeration.** A login or password-reset endpoint that
|
|
107
|
+
responds differently for "user exists" vs "user doesn't" lets an
|
|
108
|
+
attacker harvest your customer list. See
|
|
109
|
+
[OWASP WSTG-IDNT-04](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/03-Identity_Management_Testing/04-Testing_for_Account_Enumeration_and_Guessable_User_Account).
|
|
110
|
+
- **Bulk session invalidation.** When a user changes their password
|
|
111
|
+
because they think they were compromised, every existing token they
|
|
112
|
+
hold should stop working immediately. Most homegrown JWT layers don't
|
|
113
|
+
do this.
|
|
114
|
+
- **One-time tokens.** Verification and password-reset tokens should be
|
|
115
|
+
random, hashed at rest, single-use, and expire fast. Storing the raw
|
|
116
|
+
token in the database is a "now your DB backup is also a credential
|
|
117
|
+
dump" mistake.
|
|
118
|
+
- **Phone numbers.** SMS codes need [E.164](https://en.wikipedia.org/wiki/E.164)-validated
|
|
119
|
+
numbers, attempt limits, and an upstream provider. Wiring all of that
|
|
120
|
+
yourself for a single feature is rarely worth it.
|
|
121
|
+
|
|
122
|
+
Doing all of these correctly, with tests, is two to four weeks of
|
|
123
|
+
engineering for a competent team. Doing them once and embedding the
|
|
124
|
+
result everywhere is what regstack is for.
|
|
125
|
+
|
|
126
|
+
## What you get
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
✔ Email + password registration with email verification
|
|
130
|
+
✔ JWT login (RFC 7519) with per-token revoke AND bulk revoke
|
|
131
|
+
✔ Forgot / reset password — anti-enumeration: identical responses
|
|
132
|
+
✔ Change password (revokes old tokens) / change email (re-verify)
|
|
133
|
+
✔ Delete account
|
|
134
|
+
✔ Optional SMS two-factor (TOTP-style 6-digit codes over SMS)
|
|
135
|
+
✔ Server-side login lockout (HTTP 429 + Retry-After)
|
|
136
|
+
✔ Admin endpoints (list / disable / delete users, stats)
|
|
137
|
+
✔ Server-rendered HTML pages, theme with one CSS file
|
|
138
|
+
✔ Pluggable email (console / SMTP / Amazon SES) and SMS (Amazon SNS / Twilio)
|
|
139
|
+
✔ Argon2 password hashing, CSP-friendly templates
|
|
140
|
+
✔ Setup wizard (`regstack init`) and config validator (`regstack doctor`)
|
|
141
|
+
✔ Three storage backends: SQLite, PostgreSQL, MongoDB — chosen by URL
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Every feature is opt-in. Mount only the JSON router for a headless
|
|
145
|
+
backend; flip `enable_ui_router` to also get the bundled SSR pages.
|
|
146
|
+
Skip the SMS extras and you don't pull `twilio` or `aioboto3`.
|
|
147
|
+
|
|
148
|
+
## Why not just use…?
|
|
149
|
+
|
|
150
|
+
There are real alternatives. Here's why regstack might still be the
|
|
151
|
+
right call.
|
|
152
|
+
|
|
153
|
+
| Alternative | Why you might pick it | Why you might pick regstack instead |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| **[Auth0](https://auth0.com/) / [Clerk](https://clerk.com/) / [WorkOS](https://workos.com/) / [Stytch](https://stytch.com/)** (hosted SaaS) | Zero ops. Polished UI. Enterprise SSO out of the box. | Cost scales per-user. Your auth lives on someone else's servers. Your customer list is in their database. Vendor lock-in is real and migrations are painful. |
|
|
156
|
+
| **[Keycloak](https://www.keycloak.org/) / [Authentik](https://goauthentik.io/) / [Authelia](https://www.authelia.com/) / [Ory Kratos](https://www.ory.sh/kratos/)** (self-hosted IAM) | Full identity platform. SAML, OIDC, federation. | A separate Java/Go service to run, monitor, back up, upgrade, and reason about. Heavyweight for "let users sign up". Schema lives outside your app. |
|
|
157
|
+
| **[fastapi-users](https://fastapi-users.github.io/fastapi-users/)** | Same language, same framework. Good registration / login primitives. | Doesn't ship verification flows, anti-enumeration, bulk revoke, SMS MFA, admin endpoints, or themable pages — you build those. regstack is the longer tail. |
|
|
158
|
+
| **Roll your own** | Total control. No dependency to learn. | You re-solve every bullet from "The problem" above, including the ones you didn't know existed yet. Two to four engineering weeks, then forever to maintain. |
|
|
159
|
+
|
|
160
|
+
regstack's bet is that for most FastAPI apps the right answer is
|
|
161
|
+
**embed a small Python library that owns the boring 80% correctly, and
|
|
162
|
+
keep the user table in your own database** — not "stand up a separate
|
|
163
|
+
auth product" and not "write the boring 80% from scratch each time".
|
|
164
|
+
|
|
165
|
+
## 30-second start
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
git clone https://github.com/jdrumgoole/regstack && cd regstack
|
|
169
|
+
uv sync --extra dev
|
|
170
|
+
|
|
171
|
+
# Generate a JWT signing secret. SQLite is the default backend, no DB to install.
|
|
172
|
+
export REGSTACK_JWT_SECRET=$(python -c 'import secrets; print(secrets.token_urlsafe(64))')
|
|
173
|
+
|
|
174
|
+
uv run uvicorn examples.sqlite.main:app --reload
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Then visit <http://localhost:8000/account/login> in your browser, or
|
|
178
|
+
register from the command line:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
curl -X POST http://localhost:8000/api/auth/register \
|
|
182
|
+
-H 'content-type: application/json' \
|
|
183
|
+
-d '{"email":"alice@example.com","password":"hunter2hunter2","full_name":"Alice"}'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The bundled example serves themed SSR pages at `/account/*`, prints
|
|
187
|
+
verification / reset links and SMS codes to stdout (the `console`
|
|
188
|
+
email/SMS backends), and shows how a host overrides regstack's default
|
|
189
|
+
look by serving its own `theme.css`.
|
|
190
|
+
|
|
191
|
+
Want PostgreSQL or MongoDB instead? Set `REGSTACK_DATABASE_URL` to a
|
|
192
|
+
`postgresql+asyncpg://...` or `mongodb://...` URL and install the matching
|
|
193
|
+
extra (`uv sync --extra postgres` or `uv sync --extra mongo`). The
|
|
194
|
+
schema is created on first boot.
|
|
195
|
+
|
|
196
|
+
## Embed in your own app
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from contextlib import asynccontextmanager
|
|
200
|
+
|
|
201
|
+
from fastapi import FastAPI
|
|
202
|
+
|
|
203
|
+
from regstack import RegStack, RegStackConfig
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
config = RegStackConfig.load() # env vars + regstack.toml
|
|
207
|
+
regstack = RegStack(config=config)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@asynccontextmanager
|
|
211
|
+
async def lifespan(app: FastAPI):
|
|
212
|
+
await regstack.install_schema() # idempotent: runs migrations / creates indexes
|
|
213
|
+
yield
|
|
214
|
+
await regstack.aclose()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
app = FastAPI(lifespan=lifespan)
|
|
218
|
+
app.include_router(regstack.router, prefix=config.api_prefix)
|
|
219
|
+
app.include_router(regstack.ui_router, prefix=config.ui_prefix) # optional
|
|
220
|
+
app.mount(config.static_prefix, regstack.static_files) # optional
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
That is the whole integration. The rest of the surface area —
|
|
224
|
+
extending the user model, registering hooks (`user_registered`,
|
|
225
|
+
`password_reset`, …), supplying your own email service — is in the
|
|
226
|
+
[embedding guide](https://regstack.readthedocs.io/en/latest/embedding.html).
|
|
227
|
+
|
|
228
|
+
## Documentation
|
|
229
|
+
|
|
230
|
+
| Page | What's there |
|
|
231
|
+
|---|---|
|
|
232
|
+
| [Quickstart](https://regstack.readthedocs.io/en/latest/quickstart.html) | Install, wizard, minimal embed |
|
|
233
|
+
| [Configuration](https://regstack.readthedocs.io/en/latest/configuration.html) | Every `RegStackConfig` field, env vars, TOML layout |
|
|
234
|
+
| [Architecture](https://regstack.readthedocs.io/en/latest/architecture.html) | Façade, backends, repos, hooks, lifecycle |
|
|
235
|
+
| [Security model](https://regstack.readthedocs.io/en/latest/security.html) | Threat model, JWT scheme, anti-enumeration, MFA |
|
|
236
|
+
| [Embedding](https://regstack.readthedocs.io/en/latest/embedding.html) | Custom backends, hooks, multi-tenant |
|
|
237
|
+
| [Theming](https://regstack.readthedocs.io/en/latest/theming.html) | CSS variables, template overrides |
|
|
238
|
+
| [CLI](https://regstack.readthedocs.io/en/latest/cli.html) | `init`, `create-admin`, `doctor` |
|
|
239
|
+
| [API reference](https://regstack.readthedocs.io/en/latest/api.html) | Public types, generated from source |
|
|
240
|
+
|
|
241
|
+
The same docs are also browsable as Markdown in [`docs/`](https://github.com/jdrumgoole/regstack/tree/main/docs).
|
|
242
|
+
|
|
243
|
+
## Status
|
|
244
|
+
|
|
245
|
+
Alpha. Single-file SQLite is the default and runs with no infrastructure;
|
|
246
|
+
PostgreSQL and MongoDB backends pass the same parametrized integration
|
|
247
|
+
suite. The next tagged release is `v0.2.0`. See the
|
|
248
|
+
[changelog](https://regstack.readthedocs.io/en/latest/changelog.html)
|
|
249
|
+
for the per-milestone breakdown.
|
|
250
|
+
|
|
251
|
+
## Contributing
|
|
252
|
+
|
|
253
|
+
Issues and pull requests welcome at
|
|
254
|
+
<https://github.com/jdrumgoole/regstack>. Before opening a PR, please
|
|
255
|
+
run the test suite and the linter — both should be green:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
uv sync --extra dev
|
|
259
|
+
uv run python -m invoke test-all # SQLite + Mongo + Postgres in parallel
|
|
260
|
+
uv run python -m invoke lint # ruff + format check + mypy
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`invoke test-sqlite` is the fast inner-loop variant that needs no
|
|
264
|
+
database services. `invoke test-all` is what CI runs and what gates a
|
|
265
|
+
release. Each pytest-xdist worker isolates its own database, so the
|
|
266
|
+
full suite is safe to re-run while you iterate.
|
|
267
|
+
|
|
268
|
+
Security disclosures: see [SECURITY.md](https://github.com/jdrumgoole/regstack/blob/main/SECURITY.md).
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
[Apache License 2.0](https://github.com/jdrumgoole/regstack/blob/main/LICENSE) © 2026 Joe Drumgoole.
|