1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use scythe_backend::manifest::BackendManifest;
use scythe_core::errors::{ErrorCode, ScytheError};
/// Supported Python row type styles for generated code.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum PythonRowType {
#[default]
Dataclass,
Pydantic,
Msgspec,
}
impl PythonRowType {
/// Parse a row_type option string into a `PythonRowType`.
pub fn from_option(value: &str) -> Result<Self, ScytheError> {
match value {
"dataclass" => Ok(Self::Dataclass),
"pydantic" => Ok(Self::Pydantic),
"msgspec" => Ok(Self::Msgspec),
_ => Err(ScytheError::new(
ErrorCode::InternalError,
format!(
"invalid row_type '{}': expected 'dataclass', 'pydantic', or 'msgspec'",
value
),
)),
}
}
/// Returns the import line for the row type.
pub fn import_line(self) -> &'static str {
match self {
Self::Dataclass => "from dataclasses import dataclass",
Self::Pydantic => "from pydantic import BaseModel",
Self::Msgspec => "import msgspec",
}
}
/// Whether the row type import is a stdlib import (vs third-party).
pub fn is_stdlib_import(self) -> bool {
matches!(self, Self::Dataclass)
}
/// Build a sorted third-party import block combining the row type import
/// with the given library import line.
///
/// isort rules: bare `import` statements come before `from` statements,
/// both groups sorted by module name.
pub fn sorted_third_party_imports(self, library_import: &str) -> String {
let row_import = self.import_line();
let row_is_bare = row_import.starts_with("import ");
let lib_is_bare = library_import.starts_with("import ");
match (row_is_bare, lib_is_bare) {
// Both bare imports or both from imports: sort by module name.
(true, true) | (false, false) => {
if row_import < library_import {
format!("{row_import}\n{library_import}")
} else {
format!("{library_import}\n{row_import}")
}
}
// Row is bare, library is from: bare comes first.
(true, false) => format!("{row_import}\n{library_import}"),
// Row is from, library is bare: bare comes first.
(false, true) => format!("{library_import}\n{row_import}"),
}
}
/// Returns the decorator line (for dataclass) or empty string (for others).
pub fn decorator(self) -> &'static str {
match self {
Self::Dataclass => "@dataclass(frozen=True, slots=True)\n",
Self::Pydantic | Self::Msgspec => "",
}
}
/// Returns the class definition line including the class name.
pub fn class_def(self, class_name: &str) -> String {
match self {
Self::Dataclass => format!("class {}:", class_name),
Self::Pydantic => format!("class {}(BaseModel):", class_name),
Self::Msgspec => format!("class {}(msgspec.Struct):", class_name),
}
}
}
/// Returns `(needs_uuid, needs_any)`: whether the manifest's scalar type mappings reference
/// `uuid.UUID` or `Any` (i.e. `dict[str, Any]`), indicating which stdlib imports the generated
/// file header must emit to avoid a `NameError` at import time.
///
/// Mirrors the always-present `datetime`/`decimal` imports but emits only when actually needed,
/// following the kotlin-jdbc uuid-import precedent.
pub fn type_support_imports(manifest: &BackendManifest) -> (bool, bool) {
let mut needs_uuid = false;
let mut needs_any = false;
for value in manifest.types.scalars.values() {
if value.contains("uuid.UUID") {
needs_uuid = true;
}
if value.contains("Any") {
needs_any = true;
}
}
(needs_uuid, needs_any)
}