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