mirror of
https://github.com/openai/codex.git
synced 2026-05-05 13:51:29 +03:00
add python toolchain for datamodel-code-generator
This commit is contained in:
@@ -15,6 +15,10 @@ struct Args {
|
||||
#[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")]
|
||||
prettier: Option<PathBuf>,
|
||||
|
||||
/// Optional Ruff executable path to format generated Python files
|
||||
#[arg(long = "ruff", value_name = "RUFF_BIN")]
|
||||
ruff: Option<PathBuf>,
|
||||
|
||||
/// Include experimental API methods and fields in generated output.
|
||||
#[arg(long = "experimental")]
|
||||
experimental: bool,
|
||||
@@ -34,5 +38,12 @@ fn main() -> Result<()> {
|
||||
},
|
||||
)?;
|
||||
codex_app_server_protocol::generate_json_with_experimental(&json_out_dir, args.experimental)?;
|
||||
codex_app_server_protocol::generate_python_with_experimental(&python_out_dir, args.experimental)
|
||||
codex_app_server_protocol::generate_python_with_options(
|
||||
&python_out_dir,
|
||||
args.ruff.as_deref(),
|
||||
codex_app_server_protocol::GeneratePythonOptions {
|
||||
experimental_api: args.experimental,
|
||||
..codex_app_server_protocol::GeneratePythonOptions::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ struct Args {
|
||||
#[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")]
|
||||
prettier: Option<PathBuf>,
|
||||
|
||||
/// Optional path to the Ruff executable to format generated Python files.
|
||||
#[arg(long = "ruff", value_name = "RUFF_BIN")]
|
||||
ruff: Option<PathBuf>,
|
||||
|
||||
/// Include experimental API methods and fields in generated fixtures.
|
||||
#[arg(long = "experimental")]
|
||||
experimental: bool,
|
||||
@@ -29,6 +33,7 @@ fn main() -> Result<()> {
|
||||
codex_app_server_protocol::write_schema_fixtures_with_options(
|
||||
&schema_root,
|
||||
args.prettier.as_deref(),
|
||||
args.ruff.as_deref(),
|
||||
codex_app_server_protocol::SchemaFixtureOptions {
|
||||
experimental_api: args.experimental,
|
||||
},
|
||||
|
||||
@@ -102,6 +102,21 @@ impl Default for GenerateTsOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GeneratePythonOptions {
|
||||
pub run_ruff: bool,
|
||||
pub experimental_api: bool,
|
||||
}
|
||||
|
||||
impl Default for GeneratePythonOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
run_ruff: true,
|
||||
experimental_api: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
|
||||
generate_ts_with_options(out_dir, prettier, GenerateTsOptions::default())
|
||||
}
|
||||
@@ -244,10 +259,25 @@ pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -
|
||||
}
|
||||
|
||||
pub fn generate_python(out_dir: &Path) -> Result<()> {
|
||||
generate_python_with_experimental(out_dir, false)
|
||||
generate_python_with_options(out_dir, None, GeneratePythonOptions::default())
|
||||
}
|
||||
|
||||
pub fn generate_python_with_experimental(out_dir: &Path, experimental_api: bool) -> Result<()> {
|
||||
generate_python_with_options(
|
||||
out_dir,
|
||||
None,
|
||||
GeneratePythonOptions {
|
||||
experimental_api,
|
||||
..GeneratePythonOptions::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_python_with_options(
|
||||
out_dir: &Path,
|
||||
ruff: Option<&Path>,
|
||||
options: GeneratePythonOptions,
|
||||
) -> Result<()> {
|
||||
ensure_dir(out_dir)?;
|
||||
|
||||
let temp_json_dir = std::env::temp_dir().join(format!(
|
||||
@@ -261,7 +291,7 @@ pub fn generate_python_with_experimental(out_dir: &Path, experimental_api: bool)
|
||||
)
|
||||
})?;
|
||||
|
||||
generate_json_with_experimental(&temp_json_dir, experimental_api)?;
|
||||
generate_json_with_experimental(&temp_json_dir, options.experimental_api)?;
|
||||
let bundle_path = temp_json_dir.join("codex_app_server_protocol.schemas.json");
|
||||
let bundle = read_json_value(&bundle_path)?;
|
||||
let root_schema_path = temp_json_dir.join("codex_app_server_protocol.python.schemas.json");
|
||||
@@ -295,6 +325,10 @@ pub fn generate_python_with_experimental(out_dir: &Path, experimental_api: bool)
|
||||
fs::write(module_dir.join("py.typed"), "")
|
||||
.with_context(|| format!("Failed to write {}", module_dir.join("py.typed").display()))?;
|
||||
|
||||
if options.run_ruff {
|
||||
run_ruff_on_python_files(ruff, &module_dir)?;
|
||||
}
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_json_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -325,8 +359,7 @@ fn generate_python_models(schema_path: &Path, output_path: &Path) -> Result<()>
|
||||
.arg("--use-generic-base-class")
|
||||
.arg("--disable-timestamp")
|
||||
.arg("--formatters")
|
||||
.arg("black")
|
||||
.arg("isort");
|
||||
.arg("ruff-format");
|
||||
|
||||
let status = command.status().with_context(|| {
|
||||
format!(
|
||||
@@ -353,28 +386,25 @@ Install `uv`/`uvx` or `datamodel-codegen` to regenerate app-server Python bindin
|
||||
}
|
||||
|
||||
fn datamodel_codegen_command() -> Command {
|
||||
if command_exists("uvx") {
|
||||
let mut command = Command::new("uvx");
|
||||
if command_exists("uv") {
|
||||
// Keep the uv-managed tool environment next to the protocol crate source,
|
||||
// not under schema/python/, which is regenerated as a fixture artifact.
|
||||
let python_codegen_project = Path::new(env!("CARGO_MANIFEST_DIR")).join("python");
|
||||
let mut command = Command::new("uv");
|
||||
command
|
||||
.arg("--with")
|
||||
.arg("black")
|
||||
.arg("--with")
|
||||
.arg("isort")
|
||||
.arg("--from")
|
||||
.arg("datamodel-code-generator")
|
||||
.arg("datamodel-codegen");
|
||||
.arg("run")
|
||||
.arg("--project")
|
||||
.arg(python_codegen_project)
|
||||
.arg("--locked")
|
||||
.arg("python")
|
||||
.arg("-m")
|
||||
.arg("datamodel_code_generator");
|
||||
return command;
|
||||
}
|
||||
|
||||
if command_exists("uv") {
|
||||
let mut command = Command::new("uv");
|
||||
if command_exists("uvx") {
|
||||
let mut command = Command::new("uvx");
|
||||
command
|
||||
.arg("tool")
|
||||
.arg("run")
|
||||
.arg("--with")
|
||||
.arg("black")
|
||||
.arg("--with")
|
||||
.arg("isort")
|
||||
.arg("--from")
|
||||
.arg("datamodel-code-generator")
|
||||
.arg("datamodel-codegen");
|
||||
@@ -384,6 +414,52 @@ fn datamodel_codegen_command() -> Command {
|
||||
Command::new("datamodel-codegen")
|
||||
}
|
||||
|
||||
fn run_ruff_on_python_files(ruff: Option<&Path>, out_dir: &Path) -> Result<()> {
|
||||
let py_files = py_files_in_recursive(out_dir)?;
|
||||
if py_files.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut format_command = ruff_command(ruff)?;
|
||||
let format_status = format_command
|
||||
.arg("format")
|
||||
.args(py_files.iter().map(|p| p.as_os_str()))
|
||||
.status()
|
||||
.context("Failed to invoke Ruff format")?;
|
||||
if !format_status.success() {
|
||||
return Err(anyhow!("Ruff format failed with status {format_status}"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ruff_command(ruff: Option<&Path>) -> Result<Command> {
|
||||
if let Some(ruff_bin) = ruff {
|
||||
return Ok(Command::new(ruff_bin));
|
||||
}
|
||||
|
||||
if command_exists("uv") {
|
||||
let python_codegen_project = Path::new(env!("CARGO_MANIFEST_DIR")).join("python");
|
||||
let mut command = Command::new("uv");
|
||||
command
|
||||
.arg("run")
|
||||
.arg("--project")
|
||||
.arg(python_codegen_project)
|
||||
.arg("--locked")
|
||||
.arg("ruff");
|
||||
return Ok(command);
|
||||
}
|
||||
|
||||
if command_exists("ruff") {
|
||||
return Ok(Command::new("ruff"));
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"Ruff was requested for Python generation, but no Ruff command is available. \
|
||||
Install `uv` for the vendored Python toolchain or pass `--ruff /path/to/ruff`."
|
||||
))
|
||||
}
|
||||
|
||||
fn command_exists(command: &str) -> bool {
|
||||
std::env::var_os("PATH").is_some_and(|paths| {
|
||||
std::env::split_paths(&paths).any(|path| {
|
||||
@@ -2289,6 +2365,26 @@ fn ts_files_in_recursive(dir: &Path) -> Result<Vec<PathBuf>> {
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn py_files_in_recursive(dir: &Path) -> Result<Vec<PathBuf>> {
|
||||
let mut files = Vec::new();
|
||||
let mut stack = vec![dir.to_path_buf()];
|
||||
while let Some(d) = stack.pop() {
|
||||
for entry in
|
||||
fs::read_dir(&d).with_context(|| format!("Failed to read dir {}", d.display()))?
|
||||
{
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
stack.push(path);
|
||||
} else if path.is_file() && path.extension() == Some(OsStr::new("py")) {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
files.sort();
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
/// Generate an index.ts file that re-exports all generated types.
|
||||
/// This allows consumers to import all types from a single file.
|
||||
fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
|
||||
|
||||
@@ -5,11 +5,13 @@ mod protocol;
|
||||
mod schema_fixtures;
|
||||
|
||||
pub use experimental_api::*;
|
||||
pub use export::GeneratePythonOptions;
|
||||
pub use export::GenerateTsOptions;
|
||||
pub use export::generate_json;
|
||||
pub use export::generate_json_with_experimental;
|
||||
pub use export::generate_python;
|
||||
pub use export::generate_python_with_experimental;
|
||||
pub use export::generate_python_with_options;
|
||||
pub use export::generate_ts;
|
||||
pub use export::generate_ts_with_options;
|
||||
pub use export::generate_types;
|
||||
|
||||
@@ -85,14 +85,19 @@ pub fn generate_typescript_schema_fixture_subtree_for_tests() -> Result<BTreeMap
|
||||
///
|
||||
/// This is intended to be used by tooling (e.g., `just write-app-server-schema`).
|
||||
/// It deletes any previously generated files so stale artifacts are removed.
|
||||
pub fn write_schema_fixtures(schema_root: &Path, prettier: Option<&Path>) -> Result<()> {
|
||||
write_schema_fixtures_with_options(schema_root, prettier, SchemaFixtureOptions::default())
|
||||
pub fn write_schema_fixtures(
|
||||
schema_root: &Path,
|
||||
prettier: Option<&Path>,
|
||||
ruff: Option<&Path>,
|
||||
) -> Result<()> {
|
||||
write_schema_fixtures_with_options(schema_root, prettier, ruff, SchemaFixtureOptions::default())
|
||||
}
|
||||
|
||||
/// Regenerates schema fixtures with configurable options.
|
||||
pub fn write_schema_fixtures_with_options(
|
||||
schema_root: &Path,
|
||||
prettier: Option<&Path>,
|
||||
ruff: Option<&Path>,
|
||||
options: SchemaFixtureOptions,
|
||||
) -> Result<()> {
|
||||
let typescript_out_dir = schema_root.join("typescript");
|
||||
@@ -112,7 +117,14 @@ pub fn write_schema_fixtures_with_options(
|
||||
},
|
||||
)?;
|
||||
crate::generate_json_with_experimental(&json_out_dir, options.experimental_api)?;
|
||||
crate::generate_python_with_experimental(&python_out_dir, options.experimental_api)?;
|
||||
crate::generate_python_with_options(
|
||||
&python_out_dir,
|
||||
ruff,
|
||||
crate::GeneratePythonOptions {
|
||||
experimental_api: options.experimental_api,
|
||||
..crate::GeneratePythonOptions::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user