mirror of
https://github.com/fastapi/full-stack-fastapi-template.git
synced 2026-11-03 20:36:27 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b632d4f88 | |||
| bbe269d382 | |||
| 486f054cc8 | |||
| f8dd304eeb | |||
| 2ccfa25845 | |||
| f8ba0b81cd |
@@ -28,7 +28,7 @@ jobs:
|
||||
fetch-depth: 2
|
||||
persist-credentials: false
|
||||
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
||||
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
|
||||
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
|
||||
uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
allowed-skips: test-playwright
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Get PR Push token
|
||||
id: pr-push
|
||||
if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true'
|
||||
uses: tiangolo/pr-push@ff4e51a433de4c22bbf90597e069e8247b9203d2 # 0.0.1
|
||||
uses: tiangolo/pr-push@f336b3817f32ea9b8273a8c15f8ecb739ac38167 # 0.0.4
|
||||
- name: Commit and push changes
|
||||
if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true'
|
||||
env:
|
||||
@@ -109,6 +109,6 @@ jobs:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
|
||||
uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
with:
|
||||
version: "latest-known"
|
||||
- run: docker compose down -v --remove-orphans
|
||||
- run: docker compose up -d db mailpit
|
||||
- run: docker compose up -d --wait db mailpit
|
||||
- name: Migrate DB
|
||||
run: uv run bash scripts/prestart.sh
|
||||
working-directory: backend
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
|
||||
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
|
||||
with:
|
||||
advanced-security: ${{ github.repository_owner == 'fastapi' }}
|
||||
annotations: ${{ github.repository_owner != 'fastapi' }}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ If your stack is already up and you just want to run the tests, you can use:
|
||||
docker compose exec backend bash scripts/tests-start.sh
|
||||
```
|
||||
|
||||
The `/app/backend/scripts/tests-start.sh` script calls `pytest` after making sure that the rest of the stack is running. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded.
|
||||
The `/app/backend/scripts/tests-start.sh` script calls `pytest`. If you need to pass extra arguments to `pytest`, you can pass them to that command and they will be forwarded.
|
||||
|
||||
For example, to stop on first error:
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import logging
|
||||
|
||||
from sqlalchemy import Engine
|
||||
from sqlmodel import Session, select
|
||||
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from app.core.db import engine
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
max_tries = 60 * 5 # 5 minutes
|
||||
wait_seconds = 1
|
||||
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(max_tries),
|
||||
wait=wait_fixed(wait_seconds),
|
||||
before=before_log(logger, logging.INFO),
|
||||
after=after_log(logger, logging.WARN),
|
||||
)
|
||||
def init(db_engine: Engine) -> None:
|
||||
try:
|
||||
with Session(db_engine) as session:
|
||||
# Try to create session to check if DB is awake
|
||||
session.exec(select(1))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logger.info("Initializing service")
|
||||
init(engine)
|
||||
logger.info("Service finished initializing")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -4,7 +4,7 @@ from app import crud
|
||||
from app.core.config import settings
|
||||
from app.models import User, UserCreate
|
||||
|
||||
engine = create_engine(str(settings.DATABASE_URL))
|
||||
engine = create_engine(str(settings.DATABASE_URL), pool_pre_ping=True)
|
||||
|
||||
|
||||
# make sure all SQLModel models are imported (app.models) before initializing DB
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import logging
|
||||
|
||||
from sqlalchemy import Engine
|
||||
from sqlmodel import Session, select
|
||||
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from app.core.db import engine
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
max_tries = 60 * 5 # 5 minutes
|
||||
wait_seconds = 1
|
||||
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(max_tries),
|
||||
wait=wait_fixed(wait_seconds),
|
||||
before=before_log(logger, logging.INFO),
|
||||
after=after_log(logger, logging.WARN),
|
||||
)
|
||||
def init(db_engine: Engine) -> None:
|
||||
try:
|
||||
# Try to create session to check if DB is awake
|
||||
with Session(db_engine) as session:
|
||||
session.exec(select(1))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logger.info("Initializing service")
|
||||
init(engine)
|
||||
logger.info("Service finished initializing")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -7,7 +7,6 @@ dependencies = [
|
||||
"fastapi[standard]>=0.141.1,<1.0.0",
|
||||
"python-multipart<1.0.0,>=0.0.27",
|
||||
"email-validator<3.0.0.0,>=2.1.0.post1",
|
||||
"tenacity<10.0.0,>=8.2.3",
|
||||
"pydantic>2.0",
|
||||
"emails>=1.1.2,<2.0",
|
||||
"jinja2<4.0.0,>=3.1.4",
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Let the DB start
|
||||
python app/backend_pre_start.py
|
||||
|
||||
# Run migrations
|
||||
alembic upgrade head
|
||||
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
python app/tests_pre_start.py
|
||||
|
||||
bash scripts/test.sh "$@"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
from app.backend_pre_start import init, logger
|
||||
|
||||
|
||||
def test_init_successful_connection() -> None:
|
||||
engine_mock = MagicMock()
|
||||
|
||||
session_mock = MagicMock()
|
||||
session_mock.__enter__.return_value = session_mock
|
||||
|
||||
select1 = select(1)
|
||||
|
||||
with (
|
||||
patch("app.backend_pre_start.Session", return_value=session_mock),
|
||||
patch("app.backend_pre_start.select", return_value=select1),
|
||||
patch.object(logger, "info"),
|
||||
patch.object(logger, "error"),
|
||||
patch.object(logger, "warn"),
|
||||
):
|
||||
try:
|
||||
init(engine_mock)
|
||||
connection_successful = True
|
||||
except Exception:
|
||||
connection_successful = False
|
||||
|
||||
assert connection_successful, (
|
||||
"The database connection should be successful and not raise an exception."
|
||||
)
|
||||
|
||||
session_mock.exec.assert_called_once_with(select1)
|
||||
@@ -1,33 +0,0 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
from app.tests_pre_start import init, logger
|
||||
|
||||
|
||||
def test_init_successful_connection() -> None:
|
||||
engine_mock = MagicMock()
|
||||
|
||||
session_mock = MagicMock()
|
||||
session_mock.__enter__.return_value = session_mock
|
||||
|
||||
select1 = select(1)
|
||||
|
||||
with (
|
||||
patch("app.tests_pre_start.Session", return_value=session_mock),
|
||||
patch("app.tests_pre_start.select", return_value=select1),
|
||||
patch.object(logger, "info"),
|
||||
patch.object(logger, "error"),
|
||||
patch.object(logger, "warn"),
|
||||
):
|
||||
try:
|
||||
init(engine_mock)
|
||||
connection_successful = True
|
||||
except Exception:
|
||||
connection_successful = False
|
||||
|
||||
assert connection_successful, (
|
||||
"The database connection should be successful and not raise an exception."
|
||||
)
|
||||
|
||||
session_mock.exec.assert_called_once_with(select1)
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"prek>=0.4.11,<1.0.0",
|
||||
"typer>=0.20.0,<1.0.0",
|
||||
"typer>=0.26.1,<1.0.0",
|
||||
"zizmor>=1.28.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Simplify database readiness checks. PR [#2460](https://github.com/fastapi/full-stack-fastapi-template/pull/2460) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ✅ Simplify password reset E2E tests. PR [#2453](https://github.com/fastapi/full-stack-fastapi-template/pull/2453) by [@alejsdev](https://github.com/alejsdev).
|
||||
* 💄 Refactor email templates and components for improved styling. PR [#2451](https://github.com/fastapi/full-stack-fastapi-template/pull/2451) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ♻️ Replace Mailcatcher with Mailpit for local email testing. PR [#2436](https://github.com/fastapi/full-stack-fastapi-template/pull/2436) by [@alejsdev](https://github.com/alejsdev).
|
||||
@@ -20,6 +21,8 @@
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ Bump the github-actions group with 4 updates. PR [#2464](https://github.com/fastapi/full-stack-fastapi-template/pull/2464) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆️ Bump Typer min version to `0.26.1`. PR [#2459](https://github.com/fastapi/full-stack-fastapi-template/pull/2459) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||
* ⬆️ Bump setup-uv action to 10.0.1. PR [#2458](https://github.com/fastapi/full-stack-fastapi-template/pull/2458) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||
|
||||
## 0.12.0 (2026-08-12)
|
||||
|
||||
@@ -14,7 +14,7 @@ members = [
|
||||
[manifest.dependency-groups]
|
||||
dev = [
|
||||
{ name = "prek", specifier = ">=0.4.11,<1.0.0" },
|
||||
{ name = "typer", specifier = ">=0.20.0,<1.0.0" },
|
||||
{ name = "typer", specifier = ">=0.26.1,<1.0.0" },
|
||||
{ name = "zizmor", specifier = ">=1.28.0" },
|
||||
]
|
||||
github-actions = [{ name = "smokeshow", specifier = ">=0.5.0" }]
|
||||
@@ -82,7 +82,6 @@ dependencies = [
|
||||
{ name = "python-multipart" },
|
||||
{ name = "sentry-sdk", extra = ["fastapi"] },
|
||||
{ name = "sqlmodel" },
|
||||
{ name = "tenacity" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
@@ -110,7 +109,6 @@ requires-dist = [
|
||||
{ name = "python-multipart", specifier = ">=0.0.27,<1.0.0" },
|
||||
{ name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.66.1,<3.0.0" },
|
||||
{ name = "sqlmodel", specifier = ">=0.0.39,<1.0.0" },
|
||||
{ name = "tenacity", specifier = ">=8.2.3,<10.0.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -1284,15 +1282,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "9.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.54"
|
||||
|
||||
Reference in New Issue
Block a user