sqlcx_core/generator/python/
mod.rs1pub mod asyncpg;
2pub mod common;
3pub mod mysql_connector;
4pub mod psycopg;
5pub mod pydantic;
6pub mod sqlite3_driver;
7
8use crate::config::TargetConfig;
9use crate::error::{Result, SqlcxError};
10use crate::generator::{DriverGenerator, GeneratedFile, LanguagePlugin, SchemaGenerator};
11use crate::ir::SqlcxIR;
12
13use self::pydantic::PydanticGenerator;
14
15pub struct PythonPlugin {
16 pub schema_name: String,
17 pub driver_name: String,
18}
19
20impl PythonPlugin {
21 pub fn new(schema: &str, driver: &str) -> Result<Self> {
22 resolve_schema(schema)?;
23 resolve_driver(driver)?;
24 Ok(Self {
25 schema_name: schema.to_string(),
26 driver_name: driver.to_string(),
27 })
28 }
29}
30
31fn resolve_schema(name: &str) -> Result<Box<dyn SchemaGenerator>> {
32 match name {
33 "pydantic" => Ok(Box::new(PydanticGenerator)),
34 _ => Err(SqlcxError::UnknownSchema(name.to_string())),
35 }
36}
37
38fn resolve_driver(name: &str) -> Result<Option<Box<dyn DriverGenerator>>> {
39 match name {
40 "none" => Ok(None),
41 "psycopg" => Ok(Some(Box::new(psycopg::PsycopgGenerator))),
42 "asyncpg" => Ok(Some(Box::new(asyncpg::AsyncpgGenerator))),
43 "sqlite3" => Ok(Some(Box::new(sqlite3_driver::Sqlite3Generator))),
44 "mysql-connector" => Ok(Some(Box::new(mysql_connector::MysqlConnectorGenerator))),
45 _ => Err(SqlcxError::UnknownDriver(name.to_string())),
46 }
47}
48
49impl LanguagePlugin for PythonPlugin {
50 fn generate(&self, ir: &SqlcxIR, config: &TargetConfig) -> Result<Vec<GeneratedFile>> {
51 let schema_gen = resolve_schema(&self.schema_name)?;
52 let overrides = &config.overrides;
53
54 let mut files = vec![schema_gen.generate(ir, overrides)?];
55
56 if let Some(driver_gen) = resolve_driver(&self.driver_name)? {
57 files.extend(driver_gen.generate(ir)?);
58 }
59
60 Ok(files)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::generator::LanguagePlugin;
68 use crate::parser::DatabaseParser;
69 use crate::parser::postgres::PostgresParser;
70 use std::collections::HashMap;
71
72 fn parse_fixture_ir() -> SqlcxIR {
73 let schema_sql = include_str!("../../../../../tests/fixtures/schema.sql");
74 let queries_sql = include_str!("../../../../../tests/fixtures/queries/users.sql");
75 let parser = PostgresParser::new();
76 let (tables, enums) = parser.parse_schema(schema_sql).unwrap();
77 let queries = parser
78 .parse_queries(queries_sql, &tables, &enums, "queries/users.sql")
79 .unwrap();
80 SqlcxIR {
81 tables,
82 queries,
83 enums,
84 }
85 }
86
87 #[test]
88 fn generates_one_file_with_no_driver() {
89 let ir = parse_fixture_ir();
90 let plugin = PythonPlugin::new("pydantic", "none").unwrap();
91 let config = TargetConfig {
92 language: "python".to_string(),
93 out: "./src/db".to_string(),
94 schema: "pydantic".to_string(),
95 driver: "none".to_string(),
96 overrides: HashMap::new(),
97 };
98 let files = plugin.generate(&ir, &config).unwrap();
99 assert_eq!(files.len(), 1);
100 assert!(files.iter().any(|f| f.path == "models.py"));
101 }
102
103 #[test]
104 fn generates_models_and_queries_with_psycopg() {
105 let ir = parse_fixture_ir();
106 let plugin = PythonPlugin::new("pydantic", "psycopg").unwrap();
107 let config = TargetConfig {
108 language: "python".to_string(),
109 out: "./src/db".to_string(),
110 schema: "pydantic".to_string(),
111 driver: "psycopg".to_string(),
112 overrides: HashMap::new(),
113 };
114 let files = plugin.generate(&ir, &config).unwrap();
115 assert_eq!(files.len(), 2);
116 assert!(files.iter().any(|f| f.path == "models.py"));
117 assert!(files.iter().any(|f| f.path.ends_with("_queries.py")));
118 assert!(!files.iter().any(|f| f.path == "client.py"));
119 }
120
121 #[test]
122 fn generates_models_and_queries_with_asyncpg() {
123 let ir = parse_fixture_ir();
124 let plugin = PythonPlugin::new("pydantic", "asyncpg").unwrap();
125 let config = TargetConfig {
126 language: "python".to_string(),
127 out: "./src/db".to_string(),
128 schema: "pydantic".to_string(),
129 driver: "asyncpg".to_string(),
130 overrides: HashMap::new(),
131 };
132 let files = plugin.generate(&ir, &config).unwrap();
133 assert_eq!(files.len(), 2);
134 assert!(files.iter().any(|f| f.path == "models.py"));
135 assert!(files.iter().any(|f| f.path.ends_with("_queries.py")));
136 assert!(!files.iter().any(|f| f.path == "client.py"));
137 }
138}