fastapi-toolsets 4.1.1__tar.gz → 4.1.3__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.
Files changed (46) hide show
  1. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/PKG-INFO +1 -1
  2. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/pyproject.toml +1 -1
  3. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/__init__.py +1 -1
  4. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/pytest/utils.py +67 -18
  5. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/LICENSE +0 -0
  6. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/README.md +0 -0
  7. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/_imports.py +0 -0
  8. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/__init__.py +0 -0
  9. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/app.py +0 -0
  10. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/commands/__init__.py +0 -0
  11. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/commands/fixtures.py +0 -0
  12. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/config.py +0 -0
  13. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/pyproject.py +0 -0
  14. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/cli/utils.py +0 -0
  15. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/crud/__init__.py +0 -0
  16. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/crud/factory.py +0 -0
  17. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/crud/search.py +0 -0
  18. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/db.py +0 -0
  19. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/dependencies.py +0 -0
  20. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/exceptions/__init__.py +0 -0
  21. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/exceptions/exceptions.py +0 -0
  22. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/exceptions/handler.py +0 -0
  23. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/fixtures/__init__.py +0 -0
  24. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/fixtures/enum.py +0 -0
  25. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/fixtures/registry.py +0 -0
  26. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/fixtures/utils.py +0 -0
  27. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/logger.py +0 -0
  28. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/metrics/__init__.py +0 -0
  29. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/metrics/handler.py +0 -0
  30. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/metrics/registry.py +0 -0
  31. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/models/__init__.py +0 -0
  32. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/models/columns.py +0 -0
  33. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/models/watched.py +0 -0
  34. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/py.typed +0 -0
  35. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/pytest/__init__.py +0 -0
  36. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/pytest/plugin.py +0 -0
  37. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/schemas.py +0 -0
  38. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/__init__.py +0 -0
  39. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/abc.py +0 -0
  40. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/oauth.py +0 -0
  41. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/sources/__init__.py +0 -0
  42. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/sources/bearer.py +0 -0
  43. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/sources/cookie.py +0 -0
  44. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/sources/header.py +0 -0
  45. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/security/sources/multi.py +0 -0
  46. {fastapi_toolsets-4.1.1 → fastapi_toolsets-4.1.3}/src/fastapi_toolsets/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-toolsets
3
- Version: 4.1.1
3
+ Version: 4.1.3
4
4
  Summary: Production-ready utilities for FastAPI applications
5
5
  Keywords: fastapi,sqlalchemy,postgresql
6
6
  Author: d3vyce
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fastapi-toolsets"
3
- version = "4.1.1"
3
+ version = "4.1.3"
4
4
  description = "Production-ready utilities for FastAPI applications"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -21,4 +21,4 @@ Example usage:
21
21
  return Response(data={"user": user.username}, message="Success")
22
22
  """
23
23
 
24
- __version__ = "4.1.1"
24
+ __version__ = "4.1.3"
@@ -7,7 +7,7 @@ from typing import Any
7
7
 
8
8
  from httpx import ASGITransport, AsyncClient
9
9
  from sqlalchemy import text
10
- from sqlalchemy.engine import make_url
10
+ from sqlalchemy.engine import URL, make_url
11
11
  from sqlalchemy.ext.asyncio import (
12
12
  AsyncSession,
13
13
  async_sessionmaker,
@@ -34,12 +34,18 @@ def _get_xdist_worker(default_test_db: str) -> str:
34
34
  return os.environ.get("PYTEST_XDIST_WORKER", default_test_db)
35
35
 
36
36
 
37
- def worker_database_url(database_url: str, default_test_db: str) -> str:
37
+ def worker_database_url(
38
+ database_url: str,
39
+ default_test_db: str,
40
+ *,
41
+ prefix: str | None = None,
42
+ ) -> str:
38
43
  """Derive a per-worker database URL for pytest-xdist parallel runs.
39
44
 
40
- Appends ``_{worker_name}`` to the database name so each xdist worker
41
- operates on its own database. When not running under xdist,
42
- ``_{default_test_db}`` is appended instead.
45
+ Sets the database name to the worker name so each xdist worker operates
46
+ on its own database. When not running under xdist, *default_test_db* is
47
+ used instead. When *prefix* is provided, the name becomes
48
+ ``{prefix}_{worker}``.
43
49
 
44
50
  The worker name is read from the ``PYTEST_XDIST_WORKER`` environment
45
51
  variable (set automatically by xdist in each worker process).
@@ -48,6 +54,9 @@ def worker_database_url(database_url: str, default_test_db: str) -> str:
48
54
  database_url: Original database connection URL.
49
55
  default_test_db: Suffix appended to the database name when
50
56
  ``PYTEST_XDIST_WORKER`` is not set.
57
+ prefix: Optional prefix prepended to the worker name
58
+ (e.g. ``"test"`` → ``"test_gw0"``). Without it, the database
59
+ name is just the worker name (e.g. ``"gw0"``).
51
60
 
52
61
  Returns:
53
62
  A database URL with a worker- or default-specific database name.
@@ -55,7 +64,8 @@ def worker_database_url(database_url: str, default_test_db: str) -> str:
55
64
  worker = _get_xdist_worker(default_test_db=default_test_db)
56
65
 
57
66
  url = make_url(database_url)
58
- url = url.set(database=f"{url.database}_{worker}")
67
+ db_name = f"{prefix}_{worker}" if prefix else worker
68
+ url = url.set(database=db_name)
59
69
  return url.render_as_string(hide_password=False)
60
70
 
61
71
 
@@ -63,6 +73,9 @@ def worker_database_url(database_url: str, default_test_db: str) -> str:
63
73
  async def create_worker_database(
64
74
  database_url: str,
65
75
  default_test_db: str = "test_db",
76
+ *,
77
+ prefix: str | None = None,
78
+ server_url: str | None = None,
66
79
  ) -> AsyncGenerator[str, None]:
67
80
  """Create and drop a per-worker database for pytest-xdist isolation.
68
81
 
@@ -74,10 +87,16 @@ async def create_worker_database(
74
87
  name (e.g. ``_gw0``). Otherwise it is suffixed with *default_test_db*.
75
88
 
76
89
  Args:
77
- database_url: Original database connection URL (used as the server
78
- connection and as the base for the worker database name).
90
+ database_url: Original database connection URL (used as the base for
91
+ the worker database name).
79
92
  default_test_db: Suffix appended to the database name when
80
93
  ``PYTEST_XDIST_WORKER`` is not set. Defaults to ``"test_db"``.
94
+ prefix: Optional prefix prepended to the worker name
95
+ (e.g. ``prefix="test"`` → ``"test_gw0"``). Without it, the
96
+ database name is just the worker name (e.g. ``"gw0"``).
97
+ server_url: URL used for server-level DDL (must point to an existing
98
+ database on the same server). Defaults to *database_url* with the
99
+ database omitted, letting asyncpg fall back to the username.
81
100
 
82
101
  Yields:
83
102
  The worker-specific database URL.
@@ -86,7 +105,7 @@ async def create_worker_database(
86
105
  ```python
87
106
  from fastapi_toolsets.pytest import create_worker_database, create_db_session
88
107
 
89
- DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost/test_db"
108
+ DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost/myapp"
90
109
 
91
110
  @pytest.fixture(scope="session")
92
111
  async def worker_db_url():
@@ -102,21 +121,35 @@ async def create_worker_database(
102
121
  ```
103
122
  """
104
123
  worker_url = worker_database_url(
105
- database_url=database_url, default_test_db=default_test_db
124
+ database_url=database_url, default_test_db=default_test_db, prefix=prefix
106
125
  )
107
126
  worker_db_name = make_url(worker_url).database
108
127
  assert worker_db_name is not None
109
128
 
110
- engine = create_async_engine(database_url, isolation_level="AUTOCOMMIT")
129
+ _parsed = make_url(database_url)
130
+ _server_url = server_url or URL.create(
131
+ drivername=_parsed.drivername,
132
+ username=_parsed.username,
133
+ password=_parsed.password,
134
+ host=_parsed.host,
135
+ port=_parsed.port,
136
+ query=_parsed.query,
137
+ ).render_as_string(hide_password=False)
138
+
139
+ engine = create_async_engine(_server_url, isolation_level="AUTOCOMMIT")
111
140
  try:
112
141
  async with engine.connect() as conn:
113
- await conn.execute(text(f"DROP DATABASE IF EXISTS {worker_db_name}"))
114
- await create_database(db_name=worker_db_name, server_url=database_url)
142
+ await conn.execute(
143
+ text(f"DROP DATABASE IF EXISTS {worker_db_name} WITH (FORCE)")
144
+ )
145
+ await create_database(db_name=worker_db_name, server_url=_server_url)
115
146
 
116
147
  yield worker_url
117
148
 
118
149
  async with engine.connect() as conn:
119
- await conn.execute(text(f"DROP DATABASE IF EXISTS {worker_db_name}"))
150
+ await conn.execute(
151
+ text(f"DROP DATABASE IF EXISTS {worker_db_name} WITH (FORCE)")
152
+ )
120
153
  finally:
121
154
  await engine.dispose()
122
155
 
@@ -126,6 +159,7 @@ async def create_async_client(
126
159
  app: Any,
127
160
  base_url: str = "http://test",
128
161
  dependency_overrides: dict[Callable[..., Any], Callable[..., Any]] | None = None,
162
+ **kwargs: Any,
129
163
  ) -> AsyncGenerator[AsyncClient, None]:
130
164
  """Create an async httpx client for testing FastAPI applications.
131
165
 
@@ -135,6 +169,9 @@ async def create_async_client(
135
169
  dependency_overrides: Optional mapping of original dependencies to
136
170
  their test replacements. Applied via ``app.dependency_overrides``
137
171
  before yielding and cleaned up after.
172
+ **kwargs: Additional keyword arguments forwarded to
173
+ :class:`httpx.AsyncClient` (e.g. ``headers``, ``cookies``,
174
+ ``auth``, ``timeout``).
138
175
 
139
176
  Yields:
140
177
  An AsyncClient configured for the app.
@@ -182,7 +219,9 @@ async def create_async_client(
182
219
 
183
220
  transport = ASGITransport(app=app)
184
221
  try:
185
- async with AsyncClient(transport=transport, base_url=base_url) as client:
222
+ async with AsyncClient(
223
+ transport=transport, base_url=base_url, **kwargs
224
+ ) as client:
186
225
  yield client
187
226
  finally:
188
227
  if dependency_overrides:
@@ -199,6 +238,8 @@ async def create_db_session(
199
238
  expire_on_commit: bool = False,
200
239
  drop_tables: bool = True,
201
240
  cleanup: bool = False,
241
+ engine_kwargs: dict[str, Any] | None = None,
242
+ session_kwargs: dict[str, Any] | None = None,
202
243
  ) -> AsyncGenerator[AsyncSession, None]:
203
244
  """Create a database session for testing.
204
245
 
@@ -213,6 +254,12 @@ async def create_db_session(
213
254
  drop_tables: Drop tables after test. Defaults to True.
214
255
  cleanup: Truncate all tables after test using
215
256
  :func:`cleanup_tables`. Defaults to False.
257
+ engine_kwargs: Additional keyword arguments forwarded to
258
+ :func:`sqlalchemy.ext.asyncio.create_async_engine`
259
+ (e.g. ``pool_size``, ``connect_args``).
260
+ session_kwargs: Additional keyword arguments forwarded to
261
+ :class:`sqlalchemy.ext.asyncio.async_sessionmaker`
262
+ (e.g. ``autoflush``, ``class_``).
216
263
 
217
264
  Yields:
218
265
  An AsyncSession ready for database operations.
@@ -237,15 +284,17 @@ async def create_db_session(
237
284
  await db_session.commit()
238
285
  ```
239
286
  """
240
- engine = create_async_engine(database_url, echo=echo)
287
+ engine = create_async_engine(database_url, echo=echo, **(engine_kwargs or {}))
241
288
 
242
289
  try:
243
- # Create tables
244
290
  async with engine.begin() as conn:
245
291
  await conn.run_sync(base.metadata.create_all)
246
292
 
247
293
  session_maker = async_sessionmaker(
248
- engine, expire_on_commit=expire_on_commit, class_=EventSession
294
+ engine,
295
+ expire_on_commit=expire_on_commit,
296
+ class_=EventSession,
297
+ **(session_kwargs or {}),
249
298
  )
250
299
  async with session_maker() as session:
251
300
  yield session