iceaxe 0.7.1__cp313-cp313-macosx_11_0_arm64.whl

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.

Potentially problematic release.


This version of iceaxe might be problematic. Click here for more details.

Files changed (75) hide show
  1. iceaxe/__init__.py +20 -0
  2. iceaxe/__tests__/__init__.py +0 -0
  3. iceaxe/__tests__/benchmarks/__init__.py +0 -0
  4. iceaxe/__tests__/benchmarks/test_bulk_insert.py +45 -0
  5. iceaxe/__tests__/benchmarks/test_select.py +114 -0
  6. iceaxe/__tests__/conf_models.py +133 -0
  7. iceaxe/__tests__/conftest.py +204 -0
  8. iceaxe/__tests__/docker_helpers.py +208 -0
  9. iceaxe/__tests__/helpers.py +268 -0
  10. iceaxe/__tests__/migrations/__init__.py +0 -0
  11. iceaxe/__tests__/migrations/conftest.py +36 -0
  12. iceaxe/__tests__/migrations/test_action_sorter.py +237 -0
  13. iceaxe/__tests__/migrations/test_generator.py +140 -0
  14. iceaxe/__tests__/migrations/test_generics.py +91 -0
  15. iceaxe/__tests__/mountaineer/__init__.py +0 -0
  16. iceaxe/__tests__/mountaineer/dependencies/__init__.py +0 -0
  17. iceaxe/__tests__/mountaineer/dependencies/test_core.py +76 -0
  18. iceaxe/__tests__/schemas/__init__.py +0 -0
  19. iceaxe/__tests__/schemas/test_actions.py +1264 -0
  20. iceaxe/__tests__/schemas/test_cli.py +25 -0
  21. iceaxe/__tests__/schemas/test_db_memory_serializer.py +1525 -0
  22. iceaxe/__tests__/schemas/test_db_serializer.py +398 -0
  23. iceaxe/__tests__/schemas/test_db_stubs.py +190 -0
  24. iceaxe/__tests__/test_alias.py +83 -0
  25. iceaxe/__tests__/test_base.py +52 -0
  26. iceaxe/__tests__/test_comparison.py +383 -0
  27. iceaxe/__tests__/test_field.py +11 -0
  28. iceaxe/__tests__/test_helpers.py +9 -0
  29. iceaxe/__tests__/test_modifications.py +151 -0
  30. iceaxe/__tests__/test_queries.py +605 -0
  31. iceaxe/__tests__/test_queries_str.py +173 -0
  32. iceaxe/__tests__/test_session.py +1511 -0
  33. iceaxe/__tests__/test_text_search.py +287 -0
  34. iceaxe/alias_values.py +67 -0
  35. iceaxe/base.py +350 -0
  36. iceaxe/comparison.py +560 -0
  37. iceaxe/field.py +250 -0
  38. iceaxe/functions.py +906 -0
  39. iceaxe/generics.py +140 -0
  40. iceaxe/io.py +107 -0
  41. iceaxe/logging.py +91 -0
  42. iceaxe/migrations/__init__.py +5 -0
  43. iceaxe/migrations/action_sorter.py +98 -0
  44. iceaxe/migrations/cli.py +228 -0
  45. iceaxe/migrations/client_io.py +62 -0
  46. iceaxe/migrations/generator.py +404 -0
  47. iceaxe/migrations/migration.py +86 -0
  48. iceaxe/migrations/migrator.py +101 -0
  49. iceaxe/modifications.py +176 -0
  50. iceaxe/mountaineer/__init__.py +10 -0
  51. iceaxe/mountaineer/cli.py +74 -0
  52. iceaxe/mountaineer/config.py +46 -0
  53. iceaxe/mountaineer/dependencies/__init__.py +6 -0
  54. iceaxe/mountaineer/dependencies/core.py +67 -0
  55. iceaxe/postgres.py +133 -0
  56. iceaxe/py.typed +0 -0
  57. iceaxe/queries.py +1455 -0
  58. iceaxe/queries_str.py +294 -0
  59. iceaxe/schemas/__init__.py +0 -0
  60. iceaxe/schemas/actions.py +864 -0
  61. iceaxe/schemas/cli.py +30 -0
  62. iceaxe/schemas/db_memory_serializer.py +705 -0
  63. iceaxe/schemas/db_serializer.py +346 -0
  64. iceaxe/schemas/db_stubs.py +525 -0
  65. iceaxe/session.py +860 -0
  66. iceaxe/session_optimized.c +12035 -0
  67. iceaxe/session_optimized.cpython-313-darwin.so +0 -0
  68. iceaxe/session_optimized.pyx +212 -0
  69. iceaxe/sql_types.py +148 -0
  70. iceaxe/typing.py +73 -0
  71. iceaxe-0.7.1.dist-info/METADATA +261 -0
  72. iceaxe-0.7.1.dist-info/RECORD +75 -0
  73. iceaxe-0.7.1.dist-info/WHEEL +6 -0
  74. iceaxe-0.7.1.dist-info/licenses/LICENSE +21 -0
  75. iceaxe-0.7.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,237 @@
1
+ import pytest
2
+
3
+ from iceaxe.migrations.action_sorter import ActionTopologicalSorter
4
+ from iceaxe.schemas.actions import DatabaseActions
5
+ from iceaxe.schemas.db_stubs import DBObject
6
+
7
+
8
+ class MockNode(DBObject):
9
+ name: str
10
+ table_name: str = "None"
11
+
12
+ model_config = {
13
+ "frozen": True,
14
+ }
15
+
16
+ def representation(self):
17
+ return f"MockNode({self.name}, {self.table_name})"
18
+
19
+ async def create(self, actor: DatabaseActions):
20
+ pass
21
+
22
+ async def migrate(self, previous: DBObject, actor: DatabaseActions):
23
+ pass
24
+
25
+ async def destroy(self, actor: DatabaseActions):
26
+ pass
27
+
28
+ def __hash__(self):
29
+ return hash(self.representation())
30
+
31
+
32
+ def custom_topological_sort(graph_edges):
33
+ sorter = ActionTopologicalSorter(graph_edges)
34
+ sorted_objects = sorter.sort()
35
+ return {obj: i for i, obj in enumerate(sorted_objects)}
36
+
37
+
38
+ def test_simple_dag():
39
+ A = MockNode(name="A")
40
+ B = MockNode(name="B")
41
+ C = MockNode(name="C")
42
+ D = MockNode(name="D")
43
+ graph = {D: [B, C], C: [A], B: [A], A: []}
44
+ result = custom_topological_sort(graph)
45
+ assert list(result.keys()) == [A, C, B, D]
46
+
47
+
48
+ def test_disconnected_graph():
49
+ A = MockNode(name="A")
50
+ B = MockNode(name="B")
51
+ C = MockNode(name="C")
52
+ D = MockNode(name="D")
53
+ E = MockNode(name="E")
54
+ graph = {B: [A], A: [], D: [C], C: [], E: []}
55
+ result = custom_topological_sort(graph)
56
+ assert set(result.keys()) == {A, B, C, D, E}
57
+ assert result[A] < result[B]
58
+ assert result[C] < result[D]
59
+
60
+
61
+ def test_single_table_grouping():
62
+ A = MockNode(name="A", table_name="table1")
63
+ B = MockNode(name="B", table_name="table1")
64
+ C = MockNode(name="C", table_name="table1")
65
+ graph = {C: [], B: [C], A: [B]}
66
+ result = custom_topological_sort(graph)
67
+ assert list(result.keys()) == [C, B, A]
68
+
69
+
70
+ def test_multiple_table_grouping():
71
+ A = MockNode(name="A", table_name="table1")
72
+ B = MockNode(name="B", table_name="table1")
73
+ C = MockNode(name="C", table_name="table2")
74
+ D = MockNode(name="D", table_name="table2")
75
+ E = MockNode(name="E", table_name="table3")
76
+ graph = {E: [], D: [], C: [D, E], A: [C], B: [C]}
77
+ result = custom_topological_sort(graph)
78
+ assert set(result.keys()) == {A, B, C, D, E}
79
+ assert result[C] < result[A] and result[C] < result[B]
80
+ assert result[D] < result[C] and result[E] < result[C]
81
+
82
+
83
+ def test_cross_table_references():
84
+ A = MockNode(name="A", table_name="table1")
85
+ B = MockNode(name="B", table_name="table2")
86
+ C = MockNode(name="C", table_name="table1")
87
+ D = MockNode(name="D", table_name="table2")
88
+ graph = {D: [], C: [D], B: [C], A: [B]}
89
+ result = custom_topological_sort(graph)
90
+ assert list(result.keys()) == [D, C, B, A]
91
+
92
+
93
+ def test_nodes_without_table_name():
94
+ A = MockNode(name="A", table_name="table1")
95
+ B = MockNode(name="B")
96
+ C = MockNode(name="C", table_name="table2")
97
+ D = MockNode(name="D")
98
+ graph = {D: [], C: [D], B: [C], A: [B]}
99
+ result = custom_topological_sort(graph)
100
+ assert list(result.keys()) == [D, C, B, A]
101
+
102
+
103
+ def test_complex_graph():
104
+ A = MockNode(name="A", table_name="table1")
105
+ B = MockNode(name="B", table_name="table1")
106
+ C = MockNode(name="C", table_name="table2")
107
+ D = MockNode(name="D", table_name="table2")
108
+ E = MockNode(name="E", table_name="table3")
109
+ F = MockNode(name="F")
110
+ G = MockNode(name="G", table_name="table3")
111
+ graph = {G: [], F: [G], E: [G], D: [F], C: [E, F], A: [C, D], B: [C]}
112
+ result = custom_topological_sort(graph)
113
+ assert set(result.keys()) == {A, B, C, D, E, F, G}
114
+ assert result[C] < result[A] and result[D] < result[A]
115
+ assert result[C] < result[B]
116
+ assert result[E] < result[C] and result[F] < result[C]
117
+ assert result[F] < result[D]
118
+ assert result[G] < result[E] and result[G] < result[F]
119
+
120
+
121
+ def test_cyclic_graph():
122
+ A = MockNode(name="A")
123
+ B = MockNode(name="B")
124
+ C = MockNode(name="C")
125
+ graph = {A: [B], B: [C], C: [A]}
126
+ with pytest.raises(ValueError, match="Graph contains a cycle"):
127
+ custom_topological_sort(graph)
128
+
129
+
130
+ def test_empty_graph():
131
+ graph = {}
132
+ result = custom_topological_sort(graph)
133
+ assert result == {}
134
+
135
+
136
+ def test_single_node_graph():
137
+ A = MockNode(name="A")
138
+ graph = {A: []}
139
+ result = custom_topological_sort(graph)
140
+ assert result == {A: 0}
141
+
142
+
143
+ def test_all_nodes_same_table():
144
+ A = MockNode(name="A", table_name="table1")
145
+ B = MockNode(name="B", table_name="table1")
146
+ C = MockNode(name="C", table_name="table1")
147
+ D = MockNode(name="D", table_name="table1")
148
+ graph = {D: [], B: [D], C: [D], A: [B, C]}
149
+ result = custom_topological_sort(graph)
150
+ assert list(result.keys()) == [D, B, C, A]
151
+
152
+
153
+ def test_mixed_node_types():
154
+ A = MockNode(name="A", table_name="table1")
155
+ B = MockNode(name="B")
156
+ C = MockNode(name="C", table_name="table2")
157
+ D = MockNode(name="D", table_name="table3")
158
+ graph = {D: [], C: [D], B: [C], A: [B]}
159
+ result = custom_topological_sort(graph)
160
+ assert list(result.keys()) == [D, C, B, A]
161
+
162
+
163
+ def test_large_graph_performance():
164
+ import random
165
+ import string
166
+ import time
167
+
168
+ def generate_large_graph(size):
169
+ nodes = [MockNode(name=c) for c in string.ascii_uppercase] + [
170
+ MockNode(name=f"N{i}") for i in range(size - 26)
171
+ ]
172
+ graph = {node: set() for node in nodes}
173
+ for i, node in enumerate(nodes):
174
+ graph[node] = set(random.sample(nodes[i + 1 :], min(5, len(nodes) - i - 1)))
175
+ return graph
176
+
177
+ large_graph = generate_large_graph(1000)
178
+ start_time = time.time()
179
+ result = custom_topological_sort(large_graph)
180
+ end_time = time.time()
181
+ assert len(result) == 1000
182
+ assert end_time - start_time < 5
183
+
184
+
185
+ def test_graph_with_isolated_nodes():
186
+ A = MockNode(name="A")
187
+ B = MockNode(name="B")
188
+ C = MockNode(name="C")
189
+ D = MockNode(name="D")
190
+ E = MockNode(name="E")
191
+ graph = {A: [B], B: [], C: [], D: [E], E: []}
192
+ result = custom_topological_sort(graph)
193
+ assert set(result.keys()) == {A, B, C, D, E}
194
+ assert result[B] < result[A]
195
+ assert result[E] < result[D]
196
+
197
+
198
+ @pytest.mark.parametrize(
199
+ "graph, expected_order",
200
+ [
201
+ (
202
+ {
203
+ MockNode(name="C"): set(),
204
+ MockNode(name="B"): {MockNode(name="C")},
205
+ MockNode(name="A"): {MockNode(name="B")},
206
+ },
207
+ ["C", "B", "A"],
208
+ ),
209
+ (
210
+ {
211
+ MockNode(name="D"): set(),
212
+ MockNode(name="B"): {MockNode(name="D")},
213
+ MockNode(name="C"): {MockNode(name="D")},
214
+ MockNode(name="A"): {MockNode(name="B"), MockNode(name="C")},
215
+ },
216
+ ["D", "B", "C", "A"],
217
+ ),
218
+ ],
219
+ )
220
+ def test_various_graph_structures(graph, expected_order):
221
+ result = custom_topological_sort(graph)
222
+ assert [node.name for node in result.keys()] == expected_order
223
+
224
+
225
+ def test_consistent_results():
226
+ """
227
+ Test for consistent results with same input
228
+
229
+ """
230
+ A = MockNode(name="A", table_name="table1")
231
+ B = MockNode(name="B", table_name="table1")
232
+ C = MockNode(name="C", table_name="table2")
233
+ D = MockNode(name="D", table_name="table2")
234
+ graph = {D: [], C: [D], B: [D], A: [B, C]}
235
+ result1 = custom_topological_sort(graph)
236
+ result2 = custom_topological_sort(graph)
237
+ assert list(result1.keys()) == list(result2.keys())
@@ -0,0 +1,140 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Any
4
+
5
+ import pytest
6
+ from pydantic import BaseModel
7
+
8
+ from iceaxe.migrations.generator import MigrationGenerator
9
+ from iceaxe.migrations.migration import MigrationRevisionBase
10
+ from iceaxe.schemas.actions import ColumnType, DatabaseActions, DryRunAction
11
+ from iceaxe.schemas.db_memory_serializer import DatabaseMemorySerializer
12
+ from iceaxe.schemas.db_stubs import DBTable
13
+
14
+
15
+ @pytest.mark.asyncio
16
+ async def test_new_migration():
17
+ migration_generator = MigrationGenerator()
18
+
19
+ code, up_revision = await migration_generator.new_migration(
20
+ down_objects_with_dependencies=[
21
+ (
22
+ DBTable(
23
+ table_name="test_table_a",
24
+ ),
25
+ [],
26
+ )
27
+ ],
28
+ up_objects_with_dependencies=[
29
+ (
30
+ DBTable(
31
+ table_name="test_table_b",
32
+ ),
33
+ [],
34
+ )
35
+ ],
36
+ down_revision="test_down_revision",
37
+ user_message="test_user_message",
38
+ )
39
+
40
+ # Expected up
41
+ assert 'await migrator.actor.add_table(table_name="test_table_b")' in code
42
+ assert 'await migrator.actor.drop_table(table_name="test_table_a")' in code
43
+
44
+ # Expected down
45
+ assert 'await migrator.actor.drop_table(table_name="test_table_b")' in code
46
+ assert 'await migrator.actor.add_table(table_name="test_table_a")' in code
47
+
48
+ assert "Context: test_user_message" in code
49
+ assert 'down_revision: str | None = "test_down_revision"' in code
50
+
51
+
52
+ def test_actions_to_code():
53
+ actor = DatabaseActions()
54
+ migration_generator = MigrationGenerator()
55
+
56
+ code = migration_generator.actions_to_code(
57
+ [
58
+ DryRunAction(
59
+ fn=actor.add_column,
60
+ kwargs={
61
+ "table_name": "test_table",
62
+ "column_name": "test_column",
63
+ "explicit_data_type": ColumnType.VARCHAR,
64
+ },
65
+ )
66
+ ]
67
+ )
68
+ assert code == [
69
+ 'await migrator.actor.add_column(table_name="test_table", column_name="test_column", explicit_data_type=ColumnType.VARCHAR)'
70
+ ]
71
+
72
+
73
+ def test_actions_to_code_pass():
74
+ """
75
+ We support generating migrations where there are no schema-level changes, so users can
76
+ write their own data migration logic. In these cases we should pass the code-block
77
+ so the resulting file is still legitimate.
78
+
79
+ """
80
+ migration_generator = MigrationGenerator()
81
+ code = migration_generator.actions_to_code([])
82
+ assert code == ["pass"]
83
+
84
+
85
+ class ExampleEnum(Enum):
86
+ A = "a"
87
+ B = "b"
88
+
89
+
90
+ class ExampleModel(BaseModel):
91
+ value: str
92
+
93
+
94
+ @dataclass
95
+ class ExampleDataclass:
96
+ value: str
97
+
98
+
99
+ @pytest.mark.parametrize(
100
+ "value, expected_value",
101
+ [
102
+ (ExampleEnum.A, "ExampleEnum.A"),
103
+ ("example_arg", '"example_arg"'),
104
+ (1, "1"),
105
+ (
106
+ {"key": "value", "nested": {"key": "value2"}},
107
+ '{"key": "value", "nested": {"key": "value2"}}',
108
+ ),
109
+ (
110
+ {
111
+ "key": ExampleModel(value="test"),
112
+ "enum": ExampleEnum.B,
113
+ },
114
+ '{"key": ExampleModel(value="test"), "enum": ExampleEnum.B}',
115
+ ),
116
+ (ExampleModel(value="test"), 'ExampleModel(value="test")'),
117
+ (ExampleDataclass(value="test"), 'ExampleDataclass(value="test")'),
118
+ (True, "True"),
119
+ (False, "False"),
120
+ (frozenset({"A", "B"}), 'frozenset({"A", "B"})'),
121
+ ({"A", "B"}, '{"A", "B"}'),
122
+ (("A",), '("A",)'),
123
+ (("A", "B"), '("A", "B")'),
124
+ ],
125
+ )
126
+ def test_format_arg(value: Any, expected_value: str):
127
+ migration_generator = MigrationGenerator()
128
+ assert migration_generator.format_arg(value) == expected_value
129
+
130
+
131
+ def test_track_import():
132
+ migration_generator = MigrationGenerator()
133
+
134
+ migration_generator.track_import(DatabaseMemorySerializer)
135
+ migration_generator.track_import(MigrationRevisionBase)
136
+
137
+ assert dict(migration_generator.import_tracker) == {
138
+ "iceaxe.migrations.migration": {"MigrationRevisionBase"},
139
+ "iceaxe.schemas.db_memory_serializer": {"DatabaseMemorySerializer"},
140
+ }
@@ -0,0 +1,91 @@
1
+ from enum import Enum
2
+ from typing import Any, Type, Union
3
+
4
+ import pytest
5
+
6
+ from iceaxe.generics import (
7
+ _is_type_compatible,
8
+ remove_null_type,
9
+ )
10
+
11
+
12
+ class SomeEnum(Enum):
13
+ A = "A"
14
+
15
+
16
+ class SomeSuperClass:
17
+ pass
18
+
19
+
20
+ class SomeSubClass(SomeSuperClass):
21
+ pass
22
+
23
+
24
+ @pytest.mark.parametrize(
25
+ "obj_type, target_type, expected",
26
+ [
27
+ # Basic types
28
+ (int, int, True),
29
+ (str, str, True),
30
+ (int, str, False),
31
+ (int, float, False),
32
+ # Subclasses
33
+ (bool, int, True),
34
+ (int, object, True),
35
+ (SomeSubClass, SomeSuperClass, True),
36
+ # Instance can match classes
37
+ (SomeSubClass(), SomeSuperClass, True),
38
+ ([SomeSubClass], list[SomeSuperClass], True),
39
+ # Enums
40
+ (SomeEnum, Type[Enum], True),
41
+ # Unions with new syntax
42
+ (int, Union[int, str], True),
43
+ (str, Union[int, str], True),
44
+ (float, Union[int, str], False),
45
+ # Unions with old syntax using type hints
46
+ (int, Union[int, str], True),
47
+ (str, Union[int, str], True),
48
+ (float, Union[int, str], False),
49
+ # Complex types involving collections
50
+ (list[int], list[int], True),
51
+ (list[int], list[str], False),
52
+ (dict[str, int], dict[str, int], True),
53
+ (dict[str, int], dict[str, str], False),
54
+ # More complex union cases
55
+ (list[int], Union[list[int], dict[str, str]], True),
56
+ (dict[str, str], Union[list[int], dict[str, str]], True),
57
+ (dict[str, float], Union[list[int], dict[str, str]], False),
58
+ (dict[str, str], Union[list[int], dict[str, Any]], True),
59
+ # Pipe operator if Python >= 3.10
60
+ (int, int | str, True),
61
+ (int | str, int | str | float, True),
62
+ (str, int | str, True),
63
+ (float, int | str, False),
64
+ # Nested unions
65
+ (int, list[int | float | str] | int | float | str, True),
66
+ # Optional types
67
+ (None, int | str | None, True),
68
+ (int, int | str | None, True),
69
+ # Value evaluation for sequences
70
+ ([1, 2, 3], list[int], True),
71
+ ([1, 2, "3"], list[int], False),
72
+ ],
73
+ )
74
+ def test_is_type_compatible(obj_type: Any, target_type: Any, expected: bool):
75
+ raw_result = _is_type_compatible(obj_type, target_type)
76
+ bool_result = raw_result != float("inf")
77
+ assert bool_result == expected
78
+
79
+
80
+ @pytest.mark.parametrize(
81
+ "typehint, expected",
82
+ [
83
+ (int, int),
84
+ (str, str),
85
+ (int | None, int),
86
+ (str | None, str),
87
+ (Union[int, None], int),
88
+ ],
89
+ )
90
+ def test_remove_null_type(typehint: Any, expected: bool):
91
+ assert remove_null_type(typehint) == expected
File without changes
File without changes
@@ -0,0 +1,76 @@
1
+ from unittest.mock import AsyncMock, MagicMock, patch
2
+
3
+ import asyncpg
4
+ import pytest
5
+
6
+ from iceaxe.mountaineer.config import DatabaseConfig
7
+ from iceaxe.mountaineer.dependencies.core import get_db_connection
8
+ from iceaxe.session import DBConnection
9
+
10
+
11
+ @pytest.fixture(autouse=True)
12
+ def mock_db_connect(mock_connection: AsyncMock):
13
+ with patch("asyncpg.connect", new_callable=AsyncMock) as mock:
14
+ mock.return_value = mock_connection
15
+
16
+ yield mock
17
+
18
+
19
+ @pytest.fixture
20
+ def mock_config():
21
+ return DatabaseConfig(
22
+ POSTGRES_HOST="test-host",
23
+ POSTGRES_PORT=5432,
24
+ POSTGRES_USER="test-user",
25
+ POSTGRES_PASSWORD="test-pass",
26
+ POSTGRES_DB="test-db",
27
+ )
28
+
29
+
30
+ @pytest.fixture
31
+ def mock_connection():
32
+ conn = AsyncMock(spec=asyncpg.Connection)
33
+ conn.close = AsyncMock()
34
+
35
+ # We need to populate the internal dsn parameters like the real query
36
+ conn._addr = ("test-host", 5432)
37
+ conn._params.user = "test-user"
38
+ conn._params.password = "test-pass"
39
+ conn._params.database = "test-db"
40
+
41
+ conn._introspect_types.return_value = (MagicMock(), MagicMock())
42
+
43
+ return conn
44
+
45
+
46
+ @pytest.mark.asyncio
47
+ async def test_get_db_connection_closes_after_yield(
48
+ mock_config: DatabaseConfig,
49
+ mock_connection: AsyncMock,
50
+ mock_db_connect: AsyncMock,
51
+ ):
52
+ mock_db_connect.return_value = mock_connection
53
+
54
+ # Get the generator
55
+ db_gen = get_db_connection(mock_config)
56
+
57
+ # Get the connection
58
+ connection = await anext(db_gen) # noqa: F821
59
+
60
+ assert isinstance(connection, DBConnection)
61
+ assert connection.conn == mock_connection
62
+ mock_db_connect.assert_called_once_with(
63
+ host=mock_config.POSTGRES_HOST,
64
+ port=mock_config.POSTGRES_PORT,
65
+ user=mock_config.POSTGRES_USER,
66
+ password=mock_config.POSTGRES_PASSWORD,
67
+ database=mock_config.POSTGRES_DB,
68
+ )
69
+
70
+ # Simulate the end of the generator's scope
71
+ try:
72
+ await db_gen.aclose()
73
+ except StopAsyncIteration:
74
+ pass
75
+
76
+ mock_connection.close.assert_called_once()
File without changes