fastapi-toolsets 4.1.3__tar.gz → 5.0.0__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.
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/PKG-INFO +2 -7
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/README.md +0 -2
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/pyproject.toml +2 -6
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/__init__.py +6 -3
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/commands/fixtures.py +21 -18
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/config.py +32 -21
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/utils.py +2 -1
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/crud/factory.py +7 -6
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/db/__init__.py +18 -0
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/db/core.py +323 -0
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/db/locks.py +185 -0
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/db/m2m.py +170 -0
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/db/testing.py +69 -0
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/db/watch.py +106 -0
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/fixtures/__init__.py +34 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/fixtures/registry.py +86 -34
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/fixtures/utils.py +126 -89
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/models/watched.py +7 -2
- fastapi_toolsets-5.0.0/src/fastapi_toolsets/pytest/plugin.py +128 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/pytest/utils.py +1 -1
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/db.py +0 -591
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/fixtures/__init__.py +0 -21
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/pytest/plugin.py +0 -210
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/__init__.py +0 -26
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/abc.py +0 -55
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/oauth.py +0 -197
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/sources/__init__.py +0 -8
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/sources/bearer.py +0 -120
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/sources/cookie.py +0 -148
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/sources/header.py +0 -67
- fastapi_toolsets-4.1.3/src/fastapi_toolsets/security/sources/multi.py +0 -71
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/LICENSE +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/_imports.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/app.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/commands/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/pyproject.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/crud/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/crud/search.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/dependencies.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/exceptions/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/exceptions/exceptions.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/exceptions/handler.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/fixtures/enum.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/logger.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/metrics/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/metrics/handler.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/metrics/registry.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/models/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/models/columns.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/py.typed +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/pytest/__init__.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/schemas.py +0 -0
- {fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-toolsets
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Production-ready utilities for FastAPI applications
|
|
5
5
|
Keywords: fastapi,sqlalchemy,postgresql
|
|
6
6
|
Author: d3vyce
|
|
@@ -29,14 +29,12 @@ Requires-Dist: asyncpg>=0.29.0
|
|
|
29
29
|
Requires-Dist: fastapi>=0.100.0
|
|
30
30
|
Requires-Dist: pydantic>=2.0
|
|
31
31
|
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
32
|
-
Requires-Dist: fastapi-toolsets[cli,metrics,pytest
|
|
32
|
+
Requires-Dist: fastapi-toolsets[cli,metrics,pytest] ; extra == 'all'
|
|
33
33
|
Requires-Dist: typer>=0.9.0 ; extra == 'cli'
|
|
34
34
|
Requires-Dist: prometheus-client>=0.20.0 ; extra == 'metrics'
|
|
35
35
|
Requires-Dist: httpx>=0.25.0 ; extra == 'pytest'
|
|
36
36
|
Requires-Dist: pytest-xdist>=3.0.0 ; extra == 'pytest'
|
|
37
37
|
Requires-Dist: pytest>=8.0.0 ; extra == 'pytest'
|
|
38
|
-
Requires-Dist: async-lru>=1.0 ; extra == 'security'
|
|
39
|
-
Requires-Dist: httpx>=0.25.0 ; extra == 'security'
|
|
40
38
|
Requires-Python: >=3.11
|
|
41
39
|
Project-URL: Homepage, https://github.com/d3vyce/fastapi-toolsets
|
|
42
40
|
Project-URL: Documentation, https://fastapi-toolsets.d3vyce.fr/
|
|
@@ -46,7 +44,6 @@ Provides-Extra: all
|
|
|
46
44
|
Provides-Extra: cli
|
|
47
45
|
Provides-Extra: metrics
|
|
48
46
|
Provides-Extra: pytest
|
|
49
|
-
Provides-Extra: security
|
|
50
47
|
Description-Content-Type: text/markdown
|
|
51
48
|
|
|
52
49
|
# FastAPI Toolsets
|
|
@@ -82,7 +79,6 @@ Install only the extras you need:
|
|
|
82
79
|
```bash
|
|
83
80
|
uv add "fastapi-toolsets[cli]"
|
|
84
81
|
uv add "fastapi-toolsets[metrics]"
|
|
85
|
-
uv add "fastapi-toolsets[security]"
|
|
86
82
|
uv add "fastapi-toolsets[pytest]"
|
|
87
83
|
```
|
|
88
84
|
|
|
@@ -108,7 +104,6 @@ uv add "fastapi-toolsets[all]"
|
|
|
108
104
|
|
|
109
105
|
### Optional
|
|
110
106
|
|
|
111
|
-
- **Security**: Composable authentication sources (`BearerTokenAuth`, `CookieAuth`, `APIKeyHeaderAuth`, `MultiAuth`) with HMAC-signed cookies and OAuth 2.0 / OIDC helpers
|
|
112
107
|
- **CLI**: Django-like command-line interface with fixture management and custom commands support
|
|
113
108
|
- **Metrics**: Prometheus metrics endpoint with provider/collector registry
|
|
114
109
|
- **Pytest Helpers**: Async test client, database session management, `pytest-xdist` support, and table cleanup utilities
|
|
@@ -31,7 +31,6 @@ Install only the extras you need:
|
|
|
31
31
|
```bash
|
|
32
32
|
uv add "fastapi-toolsets[cli]"
|
|
33
33
|
uv add "fastapi-toolsets[metrics]"
|
|
34
|
-
uv add "fastapi-toolsets[security]"
|
|
35
34
|
uv add "fastapi-toolsets[pytest]"
|
|
36
35
|
```
|
|
37
36
|
|
|
@@ -57,7 +56,6 @@ uv add "fastapi-toolsets[all]"
|
|
|
57
56
|
|
|
58
57
|
### Optional
|
|
59
58
|
|
|
60
|
-
- **Security**: Composable authentication sources (`BearerTokenAuth`, `CookieAuth`, `APIKeyHeaderAuth`, `MultiAuth`) with HMAC-signed cookies and OAuth 2.0 / OIDC helpers
|
|
61
59
|
- **CLI**: Django-like command-line interface with fixture management and custom commands support
|
|
62
60
|
- **Metrics**: Prometheus metrics endpoint with provider/collector registry
|
|
63
61
|
- **Pytest Helpers**: Async test client, database session management, `pytest-xdist` support, and table cleanup utilities
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-toolsets"
|
|
3
|
-
version = "
|
|
3
|
+
version = "5.0.0"
|
|
4
4
|
description = "Production-ready utilities for FastAPI applications"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -50,17 +50,13 @@ cli = [
|
|
|
50
50
|
metrics = [
|
|
51
51
|
"prometheus_client>=0.20.0",
|
|
52
52
|
]
|
|
53
|
-
security = [
|
|
54
|
-
"async-lru>=1.0",
|
|
55
|
-
"httpx>=0.25.0",
|
|
56
|
-
]
|
|
57
53
|
pytest = [
|
|
58
54
|
"httpx>=0.25.0",
|
|
59
55
|
"pytest-xdist>=3.0.0",
|
|
60
56
|
"pytest>=8.0.0",
|
|
61
57
|
]
|
|
62
58
|
all = [
|
|
63
|
-
"fastapi-toolsets[cli,metrics,pytest
|
|
59
|
+
"fastapi-toolsets[cli,metrics,pytest]",
|
|
64
60
|
]
|
|
65
61
|
|
|
66
62
|
[project.scripts]
|
|
@@ -7,18 +7,21 @@ Example usage:
|
|
|
7
7
|
from fastapi import FastAPI, Depends
|
|
8
8
|
from fastapi_toolsets.exceptions import init_exceptions_handlers
|
|
9
9
|
from fastapi_toolsets.crud import CrudFactory
|
|
10
|
-
from fastapi_toolsets.db import
|
|
10
|
+
from fastapi_toolsets.db import Database
|
|
11
11
|
from fastapi_toolsets.schemas import Response
|
|
12
12
|
|
|
13
|
+
db = Database("postgresql+asyncpg://postgres:postgres@localhost/app")
|
|
14
|
+
|
|
13
15
|
app = FastAPI()
|
|
16
|
+
db.install(app)
|
|
14
17
|
init_exceptions_handlers(app)
|
|
15
18
|
|
|
16
19
|
UserCrud = CrudFactory(User)
|
|
17
20
|
|
|
18
21
|
@app.get("/users/{user_id}", response_model=Response[dict])
|
|
19
|
-
async def get_user(user_id: int, session = Depends(
|
|
22
|
+
async def get_user(user_id: int, session = Depends(db)):
|
|
20
23
|
user = await UserCrud.get(session, [User.id == user_id])
|
|
21
24
|
return Response(data={"user": user.username}, message="Success")
|
|
22
25
|
"""
|
|
23
26
|
|
|
24
|
-
__version__ = "
|
|
27
|
+
__version__ = "5.0.0"
|
{fastapi_toolsets-4.1.3 → fastapi_toolsets-5.0.0}/src/fastapi_toolsets/cli/commands/fixtures.py
RENAMED
|
@@ -6,7 +6,8 @@ import typer
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
|
8
8
|
|
|
9
|
-
from ...fixtures import Context, LoadStrategy
|
|
9
|
+
from ...fixtures import Context, LoadStrategy
|
|
10
|
+
from ...logger import get_logger
|
|
10
11
|
from ..config import get_db_context, get_fixtures_registry
|
|
11
12
|
from ..utils import async_command
|
|
12
13
|
|
|
@@ -16,13 +17,14 @@ fixture_cli = typer.Typer(
|
|
|
16
17
|
no_args_is_help=True,
|
|
17
18
|
)
|
|
18
19
|
console = Console()
|
|
20
|
+
logger = get_logger()
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
@fixture_cli.command("list")
|
|
22
24
|
def list_fixtures(
|
|
23
25
|
ctx: typer.Context,
|
|
24
26
|
context: Annotated[
|
|
25
|
-
|
|
27
|
+
str | None,
|
|
26
28
|
typer.Option(
|
|
27
29
|
"--context",
|
|
28
30
|
"-c",
|
|
@@ -32,10 +34,10 @@ def list_fixtures(
|
|
|
32
34
|
) -> None:
|
|
33
35
|
"""List all registered fixtures."""
|
|
34
36
|
registry = get_fixtures_registry()
|
|
35
|
-
fixtures = registry.get_by_context(context
|
|
37
|
+
fixtures = registry.get_by_context(context) if context else registry.get_all()
|
|
36
38
|
|
|
37
39
|
if not fixtures:
|
|
38
|
-
|
|
40
|
+
logger.info("No fixtures found.")
|
|
39
41
|
return
|
|
40
42
|
|
|
41
43
|
table = Table("Name", "Contexts", "Dependencies")
|
|
@@ -46,7 +48,7 @@ def list_fixtures(
|
|
|
46
48
|
table.add_row(fixture.name, contexts, deps)
|
|
47
49
|
|
|
48
50
|
console.print(table)
|
|
49
|
-
|
|
51
|
+
logger.info("Total: %d fixture(s)", len(fixtures))
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
@fixture_cli.command("load")
|
|
@@ -54,8 +56,8 @@ def list_fixtures(
|
|
|
54
56
|
async def load(
|
|
55
57
|
ctx: typer.Context,
|
|
56
58
|
contexts: Annotated[
|
|
57
|
-
list[
|
|
58
|
-
typer.Argument(help="Contexts to load."),
|
|
59
|
+
list[str] | None,
|
|
60
|
+
typer.Argument(help="Contexts to load.", envvar="FIXTURES_CONTEXT"),
|
|
59
61
|
] = None,
|
|
60
62
|
strategy: Annotated[
|
|
61
63
|
LoadStrategy,
|
|
@@ -69,26 +71,27 @@ async def load(
|
|
|
69
71
|
] = False,
|
|
70
72
|
) -> None:
|
|
71
73
|
"""Load fixtures into the database."""
|
|
74
|
+
from ...fixtures import load_fixtures_by_context
|
|
75
|
+
|
|
72
76
|
registry = get_fixtures_registry()
|
|
73
77
|
db_context = get_db_context()
|
|
74
78
|
|
|
75
|
-
context_list =
|
|
79
|
+
context_list = contexts or [Context.BASE.value]
|
|
76
80
|
|
|
77
81
|
ordered = registry.resolve_context_dependencies(*context_list)
|
|
78
82
|
|
|
79
83
|
if not ordered:
|
|
80
|
-
|
|
84
|
+
logger.info("No fixtures to load for the specified context(s).")
|
|
81
85
|
return
|
|
82
86
|
|
|
83
|
-
print(f"\nFixtures to load ({strategy.value} strategy):")
|
|
84
|
-
for name in ordered:
|
|
85
|
-
fixture = registry.get(name)
|
|
86
|
-
instances = list(fixture.func())
|
|
87
|
-
model_name = type(instances[0]).__name__ if instances else "?"
|
|
88
|
-
print(f" - {name}: {len(instances)} {model_name}(s)")
|
|
89
|
-
|
|
90
87
|
if dry_run:
|
|
91
|
-
|
|
88
|
+
logger.info("Fixtures to load (%s strategy):", strategy.value)
|
|
89
|
+
for name in ordered:
|
|
90
|
+
variants = registry.get_load_variants(name, *context_list)
|
|
91
|
+
instances = [inst for v in variants for inst in v.func()]
|
|
92
|
+
model_name = type(instances[0]).__name__ if instances else "?"
|
|
93
|
+
logger.info(" - %s: %d %s(s)", name, len(instances), model_name)
|
|
94
|
+
logger.info("[Dry run - no changes made]")
|
|
92
95
|
return
|
|
93
96
|
|
|
94
97
|
async with db_context() as session:
|
|
@@ -97,4 +100,4 @@ async def load(
|
|
|
97
100
|
)
|
|
98
101
|
|
|
99
102
|
total = sum(len(items) for items in result.values())
|
|
100
|
-
|
|
103
|
+
logger.info("Loaded %d record(s) successfully.", total)
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import importlib
|
|
6
6
|
import sys
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
@@ -13,6 +13,8 @@ from .pyproject import find_pyproject, load_pyproject
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from ..fixtures import FixtureRegistry
|
|
15
15
|
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
def _ensure_project_in_path():
|
|
18
20
|
"""Add project root to sys.path if not installed in editable mode."""
|
|
@@ -88,19 +90,39 @@ def get_config_value(key: str, required: bool = False) -> Any | None:
|
|
|
88
90
|
return value
|
|
89
91
|
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
@overload
|
|
94
|
+
def _import_typed(
|
|
95
|
+
key: str, expected_type: type[T], *, required: Literal[True]
|
|
96
|
+
) -> T: ... # pragma: no cover
|
|
97
|
+
@overload
|
|
98
|
+
def _import_typed(
|
|
99
|
+
key: str, expected_type: type[T], *, required: bool
|
|
100
|
+
) -> T | None: ... # pragma: no cover
|
|
101
|
+
def _import_typed(key: str, expected_type: type[T], *, required: bool) -> T | None:
|
|
102
|
+
"""Import a config value by key and validate its type.
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
Raises:
|
|
105
|
+
typer.BadParameter: If required and missing, or if the imported
|
|
106
|
+
value isn't an instance of *expected_type*.
|
|
107
|
+
"""
|
|
108
|
+
import_path = get_config_value(key, required=required)
|
|
109
|
+
if not import_path:
|
|
110
|
+
return None
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
obj = import_from_string(import_path)
|
|
113
|
+
if not isinstance(obj, expected_type):
|
|
99
114
|
raise typer.BadParameter(
|
|
100
|
-
f"'
|
|
115
|
+
f"'{key}' must be a {expected_type.__name__} instance, got {type(obj).__name__}"
|
|
101
116
|
)
|
|
102
117
|
|
|
103
|
-
return
|
|
118
|
+
return obj
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_fixtures_registry() -> FixtureRegistry:
|
|
122
|
+
"""Import and return the fixtures registry from config."""
|
|
123
|
+
from ..fixtures import FixtureRegistry
|
|
124
|
+
|
|
125
|
+
return _import_typed("fixtures", FixtureRegistry, required=True)
|
|
104
126
|
|
|
105
127
|
|
|
106
128
|
def get_db_context() -> Any:
|
|
@@ -111,15 +133,4 @@ def get_db_context() -> Any:
|
|
|
111
133
|
|
|
112
134
|
def get_custom_cli() -> typer.Typer | None:
|
|
113
135
|
"""Import and return the custom CLI Typer instance from config."""
|
|
114
|
-
|
|
115
|
-
if not import_path:
|
|
116
|
-
return None
|
|
117
|
-
|
|
118
|
-
custom = import_from_string(import_path)
|
|
119
|
-
|
|
120
|
-
if not isinstance(custom, typer.Typer):
|
|
121
|
-
raise typer.BadParameter(
|
|
122
|
-
f"'custom_cli' must be a Typer instance, got {type(custom).__name__}"
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
return custom
|
|
136
|
+
return _import_typed("custom_cli", typer.Typer, required=False)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""CLI utility functions."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import functools
|
|
5
4
|
from collections.abc import Callable, Coroutine
|
|
6
5
|
from typing import Any, ParamSpec, TypeVar
|
|
@@ -24,6 +23,8 @@ def async_command(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
|
|
|
24
23
|
|
|
25
24
|
@functools.wraps(func)
|
|
26
25
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
26
|
+
import asyncio
|
|
27
|
+
|
|
27
28
|
return asyncio.run(func(*args, **kwargs))
|
|
28
29
|
|
|
29
30
|
return wrapper
|
|
@@ -22,7 +22,7 @@ from sqlalchemy.orm import DeclarativeBase, QueryableAttribute, selectinload
|
|
|
22
22
|
from sqlalchemy.sql.base import ExecutableOption
|
|
23
23
|
from sqlalchemy.sql.roles import WhereHavingRole
|
|
24
24
|
|
|
25
|
-
from ..db import
|
|
25
|
+
from ..db import transaction
|
|
26
26
|
from ..exceptions import InvalidOrderFieldError, NotFoundError
|
|
27
27
|
from ..schemas import (
|
|
28
28
|
CursorPaginatedResponse,
|
|
@@ -190,7 +190,8 @@ class AsyncCrud(Generic[ModelType]):
|
|
|
190
190
|
"""Re-query instance by PK with default_load_options applied."""
|
|
191
191
|
mapper = cls.model.__mapper__
|
|
192
192
|
pk_filters = [
|
|
193
|
-
getattr(cls.model,
|
|
193
|
+
getattr(cls.model, cast(str, col.key))
|
|
194
|
+
== getattr(instance, cast(str, col.key))
|
|
194
195
|
for col in mapper.primary_key
|
|
195
196
|
]
|
|
196
197
|
return await cls.get(session, filters=pk_filters)
|
|
@@ -716,7 +717,7 @@ class AsyncCrud(Generic[ModelType]):
|
|
|
716
717
|
Returns:
|
|
717
718
|
Created model instance, or ``Response[schema]`` when ``schema`` is given.
|
|
718
719
|
"""
|
|
719
|
-
async with
|
|
720
|
+
async with transaction(session):
|
|
720
721
|
m2m_exclude = cls._m2m_schema_fields()
|
|
721
722
|
data = (
|
|
722
723
|
obj.model_dump(exclude=m2m_exclude) if m2m_exclude else obj.model_dump()
|
|
@@ -1067,7 +1068,7 @@ class AsyncCrud(Generic[ModelType]):
|
|
|
1067
1068
|
Raises:
|
|
1068
1069
|
NotFoundError: If no record found
|
|
1069
1070
|
"""
|
|
1070
|
-
async with
|
|
1071
|
+
async with transaction(session):
|
|
1071
1072
|
m2m_exclude = cls._m2m_schema_fields()
|
|
1072
1073
|
|
|
1073
1074
|
# Eagerly load M2M relationships that will be updated so that
|
|
@@ -1127,7 +1128,7 @@ class AsyncCrud(Generic[ModelType]):
|
|
|
1127
1128
|
Returns:
|
|
1128
1129
|
Model instance
|
|
1129
1130
|
"""
|
|
1130
|
-
async with
|
|
1131
|
+
async with transaction(session):
|
|
1131
1132
|
values = obj.model_dump(exclude_unset=True)
|
|
1132
1133
|
q = insert(cls.model).values(**values)
|
|
1133
1134
|
if set_:
|
|
@@ -1189,7 +1190,7 @@ class AsyncCrud(Generic[ModelType]):
|
|
|
1189
1190
|
Returns:
|
|
1190
1191
|
``None``, or ``Response[None]`` when ``return_response=True``.
|
|
1191
1192
|
"""
|
|
1192
|
-
async with
|
|
1193
|
+
async with transaction(session):
|
|
1193
1194
|
result = await session.execute(select(cls.model).where(and_(*filters)))
|
|
1194
1195
|
objects = result.scalars().all()
|
|
1195
1196
|
for obj in objects:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Database package: the ``Database`` facade plus PostgreSQL power-tools."""
|
|
2
|
+
|
|
3
|
+
from .core import Database, transaction
|
|
4
|
+
from .locks import LockMode, advisory_lock, lock_tables
|
|
5
|
+
from .m2m import m2m_add, m2m_remove, m2m_set
|
|
6
|
+
from .watch import wait_for_row_change
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Database",
|
|
10
|
+
"LockMode",
|
|
11
|
+
"advisory_lock",
|
|
12
|
+
"lock_tables",
|
|
13
|
+
"m2m_add",
|
|
14
|
+
"m2m_remove",
|
|
15
|
+
"m2m_set",
|
|
16
|
+
"transaction",
|
|
17
|
+
"wait_for_row_change",
|
|
18
|
+
]
|