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
|
fetch-depth: 2
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
# 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
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- name: Decide whether the needed jobs succeeded or failed
|
- 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:
|
with:
|
||||||
jobs: ${{ toJSON(needs) }}
|
jobs: ${{ toJSON(needs) }}
|
||||||
allowed-skips: test-playwright
|
allowed-skips: test-playwright
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Get PR Push token
|
- name: Get PR Push token
|
||||||
id: pr-push
|
id: pr-push
|
||||||
if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true'
|
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
|
- name: Commit and push changes
|
||||||
if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true'
|
if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true'
|
||||||
env:
|
env:
|
||||||
@@ -109,6 +109,6 @@ jobs:
|
|||||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
run: echo "$GITHUB_CONTEXT"
|
run: echo "$GITHUB_CONTEXT"
|
||||||
- name: Decide whether the needed jobs succeeded or failed
|
- 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:
|
with:
|
||||||
jobs: ${{ toJSON(needs) }}
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: "latest-known"
|
version: "latest-known"
|
||||||
- run: docker compose down -v --remove-orphans
|
- 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
|
- name: Migrate DB
|
||||||
run: uv run bash scripts/prestart.sh
|
run: uv run bash scripts/prestart.sh
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run zizmor
|
- name: Run zizmor
|
||||||
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
|
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
|
||||||
with:
|
with:
|
||||||
advanced-security: ${{ github.repository_owner == 'fastapi' }}
|
advanced-security: ${{ github.repository_owner == 'fastapi' }}
|
||||||
annotations: ${{ 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
|
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:
|
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.core.config import settings
|
||||||
from app.models import User, UserCreate
|
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
|
# 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",
|
"fastapi[standard]>=0.141.1,<1.0.0",
|
||||||
"python-multipart<1.0.0,>=0.0.27",
|
"python-multipart<1.0.0,>=0.0.27",
|
||||||
"email-validator<3.0.0.0,>=2.1.0.post1",
|
"email-validator<3.0.0.0,>=2.1.0.post1",
|
||||||
"tenacity<10.0.0,>=8.2.3",
|
|
||||||
"pydantic>2.0",
|
"pydantic>2.0",
|
||||||
"emails>=1.1.2,<2.0",
|
"emails>=1.1.2,<2.0",
|
||||||
"jinja2<4.0.0,>=3.1.4",
|
"jinja2<4.0.0,>=3.1.4",
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
set -e
|
set -e
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
# Let the DB start
|
|
||||||
python app/backend_pre_start.py
|
|
||||||
|
|
||||||
# Run migrations
|
# Run migrations
|
||||||
alembic upgrade head
|
alembic upgrade head
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,4 @@
|
|||||||
set -e
|
set -e
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
python app/tests_pre_start.py
|
|
||||||
|
|
||||||
bash scripts/test.sh "$@"
|
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]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"prek>=0.4.11,<1.0.0",
|
"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",
|
"zizmor>=1.28.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
### Refactors
|
### 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).
|
* ✅ 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).
|
* 💄 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).
|
* ♻️ 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
|
### 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).
|
* ⬆️ 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)
|
## 0.12.0 (2026-08-12)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ members = [
|
|||||||
[manifest.dependency-groups]
|
[manifest.dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "prek", specifier = ">=0.4.11,<1.0.0" },
|
{ 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" },
|
{ name = "zizmor", specifier = ">=1.28.0" },
|
||||||
]
|
]
|
||||||
github-actions = [{ name = "smokeshow", specifier = ">=0.5.0" }]
|
github-actions = [{ name = "smokeshow", specifier = ">=0.5.0" }]
|
||||||
@@ -82,7 +82,6 @@ dependencies = [
|
|||||||
{ name = "python-multipart" },
|
{ name = "python-multipart" },
|
||||||
{ name = "sentry-sdk", extra = ["fastapi"] },
|
{ name = "sentry-sdk", extra = ["fastapi"] },
|
||||||
{ name = "sqlmodel" },
|
{ name = "sqlmodel" },
|
||||||
{ name = "tenacity" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -110,7 +109,6 @@ requires-dist = [
|
|||||||
{ name = "python-multipart", specifier = ">=0.0.27,<1.0.0" },
|
{ name = "python-multipart", specifier = ">=0.0.27,<1.0.0" },
|
||||||
{ name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.66.1,<3.0.0" },
|
{ name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.66.1,<3.0.0" },
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.39,<1.0.0" },
|
{ name = "sqlmodel", specifier = ">=0.0.39,<1.0.0" },
|
||||||
{ name = "tenacity", specifier = ">=8.2.3,<10.0.0" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "ty"
|
name = "ty"
|
||||||
version = "0.0.54"
|
version = "0.0.54"
|
||||||
|
|||||||
Reference in New Issue
Block a user