use crate::codegen::traits::file_writer::FileInfo;
use crate::generators::request_inputs::RequestInputModel;
use crate::ir::types::{IrInfo, IrOperation, IrSchema};
use heck::{ToPascalCase, ToSnakeCase};
use indexmap::IndexMap;
pub fn generate_project_files(
info: &IrInfo,
package_name: &str,
header: &str,
schemas: &IndexMap<String, IrSchema>,
operations: &[IrOperation],
request_inputs: &[RequestInputModel],
include_upload_file: bool,
) -> Vec<FileInfo> {
let files = vec![
pyproject_toml(info, package_name),
readme_file(info, package_name),
py_typed(package_name),
top_level_init(package_name, header, include_upload_file),
models_init(schemas, request_inputs, header),
apis_init(operations, header),
];
files
}
fn pyproject_toml(info: &IrInfo, package_name: &str) -> FileInfo {
let description = info
.description
.as_deref()
.unwrap_or("Generated Python SDK.")
.lines()
.next()
.unwrap_or("Generated Python SDK.");
let content = format!(
r#"[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "{package_name}"
version = "{version}"
description = "{description}"
requires-python = ">=3.12"
dependencies = ["httpx>=0.27"]
"#,
version = info.version,
);
FileInfo::project("pyproject.toml".to_string(), content)
}
fn readme_file(info: &IrInfo, package_name: &str) -> FileInfo {
let title = &info.title;
let version = &info.version;
let description = info
.description
.clone()
.unwrap_or_else(|| "Generated Python SDK.".to_string());
let content = format!(
"# {title}\n\n{description}\n\nVersion: `{version}`\n\nGenerated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `{package_name}`.\n"
);
FileInfo::readme("README.md".to_string(), content)
}
fn py_typed(package_name: &str) -> FileInfo {
FileInfo::project(format!("{package_name}/py.typed"), String::new())
}
fn top_level_init(package_name: &str, header: &str, include_upload_file: bool) -> FileInfo {
let mut content = String::new();
content.push_str(header);
content.push_str("from .runtime import ApiKeyAuth as ApiKeyAuth\n");
content.push_str("from .runtime import Authenticator as Authenticator\n");
content.push_str("from .runtime import BearerAuth as BearerAuth\n");
content.push_str("from .runtime import ApiResponse as ApiResponse\n");
content.push_str("from .runtime import Client as Client\n");
content.push_str("from .runtime import ApiError as ApiError\n");
if include_upload_file {
content.push_str("from .runtime import UploadFile as UploadFile\n");
}
FileInfo::project(format!("{package_name}/__init__.py"), content)
}
fn models_init(
schemas: &IndexMap<String, IrSchema>,
request_inputs: &[RequestInputModel],
header: &str,
) -> FileInfo {
let mut content = String::new();
content.push_str(header);
let mut entries: Vec<(String, String)> = Vec::new();
for (_key, schema) in schemas {
let py_name = schema.name.to_pascal_case();
let module = schema.name.to_snake_case();
entries.push((module, py_name));
}
for model in request_inputs {
let py_name = model.name.to_pascal_case();
let module = model.name.to_snake_case();
entries.push((module, py_name));
}
entries.sort_by(|a, b| a.0.cmp(&b.0));
for (module, name) in &entries {
content.push_str(&format!("from .{module} import {name} as {name}\n"));
}
FileInfo::model("__init__.py".to_string(), content)
}
fn apis_init(operations: &[IrOperation], header: &str) -> FileInfo {
let mut content = String::new();
content.push_str(header);
let mut tags: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for op in operations {
if op.tags.is_empty() {
tags.insert("default".to_string());
} else {
for tag in &op.tags {
tags.insert(tag.clone());
}
}
}
for tag in &tags {
let module = tag.to_snake_case();
let class_name = format!("{}Api", tag.to_pascal_case());
content.push_str(&format!(
"from .{module}_api import {class_name} as {class_name}\n"
));
}
FileInfo::api("__init__.py".to_string(), content)
}