openapi_nexus/generators/python/requests/
project_files.rs1use crate::codegen::traits::file_writer::FileInfo;
4use crate::generators::request_inputs::RequestInputModel;
5use crate::ir::types::{IrInfo, IrOperation, IrSchema};
6use heck::{ToPascalCase, ToSnakeCase};
7use indexmap::IndexMap;
8
9pub fn generate_project_files(
11 info: &IrInfo,
12 package_name: &str,
13 header: &str,
14 schemas: &IndexMap<String, IrSchema>,
15 operations: &[IrOperation],
16 request_inputs: &[RequestInputModel],
17 include_upload_file: bool,
18) -> Vec<FileInfo> {
19 let files = vec![
20 pyproject_toml(info, package_name),
21 readme_file(info, package_name),
22 py_typed(package_name),
23 top_level_init(package_name, header, include_upload_file),
24 models_init(schemas, request_inputs, header),
25 apis_init(operations, header),
26 ];
27
28 files
29}
30
31fn pyproject_toml(info: &IrInfo, package_name: &str) -> FileInfo {
32 let description = info
33 .description
34 .as_deref()
35 .unwrap_or("Generated Python SDK.")
36 .lines()
37 .next()
38 .unwrap_or("Generated Python SDK.");
39 let content = format!(
40 r#"[build-system]
41requires = ["hatchling"]
42build-backend = "hatchling.build"
43
44[project]
45name = "{package_name}"
46version = "{version}"
47description = "{description}"
48requires-python = ">=3.12"
49dependencies = ["requests>=2.32"]
50"#,
51 version = info.version,
52 );
53 FileInfo::project("pyproject.toml".to_string(), content)
54}
55
56fn readme_file(info: &IrInfo, package_name: &str) -> FileInfo {
57 let title = &info.title;
58 let version = &info.version;
59 let description = info
60 .description
61 .clone()
62 .unwrap_or_else(|| "Generated Python SDK.".to_string());
63 let content = format!(
64 "# {title}\n\n{description}\n\nVersion: `{version}`\n\nGenerated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `{package_name}`.\n"
65 );
66 FileInfo::readme("README.md".to_string(), content)
67}
68
69fn py_typed(package_name: &str) -> FileInfo {
70 FileInfo::project(format!("{package_name}/py.typed"), String::new())
71}
72
73fn top_level_init(package_name: &str, header: &str, include_upload_file: bool) -> FileInfo {
74 let mut content = String::new();
75 content.push_str(header);
76 content.push_str("from .runtime import ApiKeyAuth as ApiKeyAuth\n");
77 content.push_str("from .runtime import Authenticator as Authenticator\n");
78 content.push_str("from .runtime import BearerAuth as BearerAuth\n");
79 content.push_str("from .runtime import ApiResponse as ApiResponse\n");
80 content.push_str("from .runtime import Client as Client\n");
81 content.push_str("from .runtime import ApiError as ApiError\n");
82 if include_upload_file {
83 content.push_str("from .runtime import UploadFile as UploadFile\n");
84 }
85 FileInfo::project(format!("{package_name}/__init__.py"), content)
86}
87
88fn models_init(
89 schemas: &IndexMap<String, IrSchema>,
90 request_inputs: &[RequestInputModel],
91 header: &str,
92) -> FileInfo {
93 let mut content = String::new();
94 content.push_str(header);
95
96 let mut entries: Vec<(String, String)> = Vec::new();
97 for (_key, schema) in schemas {
98 let py_name = schema.name.to_pascal_case();
99 let module = schema.name.to_snake_case();
100 entries.push((module, py_name));
101 }
102 for model in request_inputs {
103 let py_name = model.name.to_pascal_case();
104 let module = model.name.to_snake_case();
105 entries.push((module, py_name));
106 }
107
108 entries.sort_by(|a, b| a.0.cmp(&b.0));
109 for (module, name) in &entries {
110 content.push_str(&format!("from .{module} import {name} as {name}\n"));
111 }
112
113 FileInfo::model("__init__.py".to_string(), content)
114}
115
116fn apis_init(operations: &[IrOperation], header: &str) -> FileInfo {
117 let mut content = String::new();
118 content.push_str(header);
119
120 let mut tags: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
121 for op in operations {
122 if op.tags.is_empty() {
123 tags.insert("default".to_string());
124 } else {
125 for tag in &op.tags {
126 tags.insert(tag.clone());
127 }
128 }
129 }
130
131 for tag in &tags {
132 let module = tag.to_snake_case();
133 let class_name = format!("{}Api", tag.to_pascal_case());
134 content.push_str(&format!(
135 "from .{module}_api import {class_name} as {class_name}\n"
136 ));
137 }
138
139 FileInfo::api("__init__.py".to_string(), content)
140}