Skip to content

Commit 958ecc7

Browse files
authored
Merge pull request #54 from NikkeTryHard/feat/issue-23-sqlalchemy-031
feat: SQLAlchemy 0.3.1 Support
2 parents d7fc1c1 + c2f902b commit 958ecc7

23 files changed

+1006
-15
lines changed

docs/research/roadmap.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ flowchart TB
257257
class P5_1 inProgress
258258
class P5_3,P5_5,P6_0,P6_2,P7_0,P7_1,P7_6,P8_0,P9_2,P9_5,P9_6,P9_7 canStart
259259
class P2_4,P2_5 done
260-
class P3_0,P3_1,P3_2,P3_3 pending
260+
class P3_0,P3_1 done
261+
class P3_2,P3_3 pending
261262
class P4_0,P4_1,P4_2,P4_3,P4_4,P4_5,P4_6 pending
262263
class P5_2,P5_4 pending
263264
class P6_1,P6_3,P6_4,P6_5 pending
@@ -811,28 +812,28 @@ The 0.3.x series focuses on database test isolation. The key insight is that dat
811812

812813
**Target**: SQLAlchemy session management.
813814

815+
**Status**: ✅ COMPLETE
816+
814817
#### Session Management
815818

816-
- [ ] Hook into `Session.commit()` to prevent actual commits
817-
> **Ref**: "Injecting SAVEPOINT and ROLLBACK TO SAVEPOINT to make DB tests I/O-free" — _Rust-Python Test Isolation Blueprint_
818-
- [ ] Wrap sessions in nested transactions (savepoints)
819-
- [ ] Handle `Session.rollback()` within tests
820-
- [ ] Support scoped session patterns
821-
- [ ] Handle session-per-request patterns
819+
- [x] Hook into `Session.commit()` to prevent actual commits
820+
- [x] Wrap sessions in nested transactions (savepoints)
821+
- [x] Handle `Session.rollback()` within tests
822+
- [x] Support scoped session patterns
823+
- [x] Handle session-per-request patterns
822824

823825
#### Engine Configuration
824826

825-
- [ ] Detect SQLAlchemy engine configuration
826-
- [ ] Apply connection pooling optimizations
827-
- [ ] Handle multiple engines (read replicas, etc.)
828-
- [ ] Support async SQLAlchemy (asyncpg, aiosqlite)
829-
- [ ] Handle engine disposal
830-
> **Ref**: "For applications using database drivers, adopt the 'dispose pattern.' Ensure that any connection pool created in the parent is explicitly discarded" — _Fork Safety of Python C-Extensions_
827+
- [x] Detect SQLAlchemy engine configuration
828+
- [x] Apply connection pooling optimizations
829+
- [x] Handle multiple engines (read replicas, etc.)
830+
- [x] Support async SQLAlchemy (asyncpg, aiosqlite)
831+
- [x] Handle engine disposal
831832

832833
#### Alembic Integration
833834

834-
- [ ] Detect Alembic migration configuration
835-
- [ ] Verify migration state matches expected
835+
- [x] Detect Alembic migration configuration
836+
- [x] Verify migration state matches expected
836837
- [ ] Support running migrations before tests
837838
- [ ] Handle migration downgrade on test database
838839
- [ ] Support migration branching

rust_tests/phase4_integration.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ fn test_payload_is_toxic_propagation() {
154154
cached_effects: vec![],
155155
markers: vec![],
156156
marker_info: vec![],
157+
reuse_db: false,
158+
create_db: false,
157159
};
158160

159161
let toxic_payload = TestPayload {
@@ -170,6 +172,8 @@ fn test_payload_is_toxic_propagation() {
170172
cached_effects: vec![],
171173
markers: vec![],
172174
marker_info: vec![],
175+
reuse_db: false,
176+
create_db: false,
173177
};
174178

175179
// Verify is_toxic is correctly set

rust_tests/proptest_coverage.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ proptest! {
150150
cached_effects: vec![],
151151
markers: vec![],
152152
marker_info: vec![],
153+
reuse_db: false,
154+
create_db: false,
153155
};
154156

155157
let serialized = bincode::serde::encode_to_vec(&payload, bincode::config::standard()).expect("Failed to serialize");

rust_tests/protocol_integration.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ fn test_serialize_test_payload() {
2626
cached_effects: vec![],
2727
markers: vec![],
2828
marker_info: vec![],
29+
reuse_db: false,
30+
create_db: false,
2931
};
3032

3133
let encoded = encode_with_length(&payload).expect("Should serialize");
@@ -106,6 +108,8 @@ fn test_roundtrip_test_payload() {
106108
cached_effects: vec![],
107109
markers: vec![],
108110
marker_info: vec![],
111+
reuse_db: false,
112+
create_db: false,
109113
};
110114

111115
let encoded = encode_with_length(&original).expect("Should serialize");
@@ -183,6 +187,8 @@ fn test_empty_fixtures_payload() {
183187
cached_effects: vec![],
184188
markers: vec![],
185189
marker_info: vec![],
190+
reuse_db: false,
191+
create_db: false,
186192
};
187193

188194
let encoded = encode_with_length(&payload).expect("Should serialize empty fixtures");
@@ -205,6 +211,8 @@ fn test_async_payload() {
205211
cached_effects: vec![],
206212
markers: vec![],
207213
marker_info: vec![],
214+
reuse_db: false,
215+
create_db: false,
208216
};
209217

210218
let encoded = encode_with_length(&payload).expect("Should serialize");

rust_tests/tagging_integrity.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ fn test_toxicity_survives_serialization_roundtrip() {
178178
cached_effects: vec![],
179179
markers: vec![],
180180
marker_info: vec![],
181+
reuse_db: false,
182+
create_db: false,
181183
};
182184

183185
// Create a safe TestPayload
@@ -195,6 +197,8 @@ fn test_toxicity_survives_serialization_roundtrip() {
195197
cached_effects: vec![],
196198
markers: vec![],
197199
marker_info: vec![],
200+
reuse_db: false,
201+
create_db: false,
198202
};
199203

200204
// Serialize using bincode (same as scheduler.rs)
@@ -314,6 +318,8 @@ def test_network_stuff():
314318
cached_effects: vec![],
315319
markers: vec![],
316320
marker_info: vec![],
321+
reuse_db: false,
322+
create_db: false,
317323
};
318324

319325
// Step 6: Serialize and deserialize (simulating IPC)

src/execution/zygote.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,11 @@ fn convert_cached_effects_to_py<'py>(
11251125
// Not converted to Python dict here - it's already in marker_info
11261126
continue;
11271127
}
1128+
crate::hooks::HookEffect::SqlAlchemyDbSetup { .. } => {
1129+
// SqlAlchemyDbSetup is handled by Python harness via marker_info
1130+
// Not converted to Python dict here - it's already in marker_info
1131+
continue;
1132+
}
11281133
crate::hooks::HookEffect::AsyncioSetup {
11291134
loop_scope,
11301135
auto_mode,

src/hooks/registry.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ pub enum HookEffect {
237237
/// List of database aliases to apply isolation to
238238
databases: Vec<String>,
239239
},
240+
/// SQLAlchemy database setup effect.
241+
/// Signals that a test requires SQLAlchemy session isolation.
242+
SqlAlchemyDbSetup {
243+
/// List of database bind keys to isolate (None = all binds).
244+
databases: Option<Vec<String>>,
245+
},
240246
/// pytest-asyncio event loop configuration
241247
///
242248
/// Parsed from @pytest.mark.asyncio(loop_scope="module") or
@@ -1082,4 +1088,21 @@ mod tests {
10821088
_ => panic!("Wrong variant"),
10831089
}
10841090
}
1091+
1092+
#[test]
1093+
fn test_sqlalchemy_db_setup_effect_serializes() {
1094+
let effect = HookEffect::SqlAlchemyDbSetup {
1095+
databases: Some(vec!["default".to_string()]),
1096+
};
1097+
let json = serde_json::to_string(&effect).unwrap();
1098+
assert!(json.contains("SqlAlchemyDbSetup"));
1099+
assert!(json.contains("default"));
1100+
}
1101+
1102+
#[test]
1103+
fn test_sqlalchemy_db_setup_effect_none_databases() {
1104+
let effect = HookEffect::SqlAlchemyDbSetup { databases: None };
1105+
let json = serde_json::to_string(&effect).unwrap();
1106+
assert!(json.contains("SqlAlchemyDbSetup"));
1107+
}
10851108
}

0 commit comments

Comments
 (0)