add ci check for generated python

This commit is contained in:
sdcoffey
2026-03-09 20:55:29 -07:00
parent 216013b8fb
commit 62ff1a454c
4 changed files with 120 additions and 48 deletions
+24
View File
@@ -65,6 +65,26 @@ jobs:
- name: cargo fmt
run: cargo fmt -- --config imports_granularity=Item --check
schema_fixtures:
name: App-server schema fixtures
runs-on: ubuntu-24.04
needs: changed
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- uses: astral-sh/setup-uv@v6
- uses: dtolnay/rust-toolchain@1.93.0
- name: Regenerate app-server schema fixtures
run: cargo run -p codex-app-server-protocol --bin write_schema_fixtures
- name: Check vendored schema fixtures are up to date
run: git diff --exit-code -- app-server-protocol/schema
cargo_shear:
name: cargo shear
runs-on: ubuntu-24.04
@@ -200,6 +220,10 @@ jobs:
with:
targets: ${{ matrix.target }}
components: clippy
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- uses: astral-sh/setup-uv@v6
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Use hermetic Cargo home (musl)
@@ -141,6 +141,7 @@ class AskForApproval1(StrEnum):
class Reject(BaseModel):
mcp_elicitations: bool
request_permissions: bool
rules: bool
sandbox_approval: bool
@@ -324,6 +325,12 @@ class PluginInstallClientRequestMethod(RootModel[Literal["plugin/install"]]):
)
class PluginUninstallClientRequestMethod(RootModel[Literal["plugin/uninstall"]]):
root: Literal["plugin/uninstall"] = Field(
..., title="Plugin/uninstallClientRequestMethod"
)
class TurnStartClientRequestMethod(RootModel[Literal["turn/start"]]):
root: Literal["turn/start"] = Field(..., title="Turn/startClientRequestMethod")
@@ -2378,6 +2385,11 @@ class PatchChangeKind(
root: AddPatchChangeKind | DeletePatchChangeKind | UpdatePatchChangeKind
class PermissionGrantScope(StrEnum):
turn = "turn"
session = "session"
class PermissionProfile(BaseModel):
file_system: FileSystemPermissions | None = None
macos: MacOsSeatbeltProfileExtensions | None = None
@@ -2421,6 +2433,10 @@ class PluginListParams(BaseModel):
)
class PluginUninstallParams(BaseModel):
plugin_id: str = Field(..., alias="pluginId")
class ProductSurface(StrEnum):
chatgpt = "chatgpt"
codex = "codex"
@@ -2625,6 +2641,10 @@ class ReasoningTextDeltaNotification(BaseModel):
class RejectConfig(BaseModel):
mcp_elicitations: bool = Field(..., description="Reject MCP elicitation prompts.")
request_permissions: bool = Field(
...,
description="Reject approval prompts related to built-in permission requests.",
)
rules: bool = Field(
..., description="Reject prompts triggered by execpolicy `prompt` rules."
)
@@ -4699,6 +4719,14 @@ class PluginInstallClientRequest(BaseModel):
params: PluginInstallParams
class PluginUninstallClientRequest(BaseModel):
id: RequestId
method: PluginUninstallClientRequestMethod = Field(
..., title="Plugin/uninstallClientRequestMethod"
)
params: PluginUninstallParams
class TurnInterruptClientRequest(BaseModel):
id: RequestId
method: TurnInterruptClientRequestMethod = Field(
@@ -5686,6 +5714,7 @@ class PermissionsRequestApprovalParams(BaseModel):
class PermissionsRequestApprovalResponse(BaseModel):
permissions: GrantedPermissionProfile
scope: PermissionGrantScope | None = "turn"
class PlanItemArg(BaseModel):
@@ -6972,6 +7001,7 @@ class ClientRequest(
| AppListClientRequest
| SkillsConfigWriteClientRequest
| PluginInstallClientRequest
| PluginUninstallClientRequest
| TurnStartClientRequest
| TurnSteerClientRequest
| TurnInterruptClientRequest
@@ -7023,6 +7053,7 @@ class ClientRequest(
| AppListClientRequest
| SkillsConfigWriteClientRequest
| PluginInstallClientRequest
| PluginUninstallClientRequest
| TurnStartClientRequest
| TurnSteerClientRequest
| TurnInterruptClientRequest
@@ -179,6 +179,7 @@ class AskForApproval1(StrEnum):
class Reject(BaseModel):
mcp_elicitations: bool
request_permissions: bool
rules: bool
sandbox_approval: bool
@@ -341,6 +342,12 @@ class PluginInstallClientRequestMethod(RootModel[Literal["plugin/install"]]):
)
class PluginUninstallClientRequestMethod(RootModel[Literal["plugin/uninstall"]]):
root: Literal["plugin/uninstall"] = Field(
..., title="Plugin/uninstallClientRequestMethod"
)
class TurnStartClientRequestMethod(RootModel[Literal["turn/start"]]):
root: Literal["turn/start"] = Field(..., title="Turn/startClientRequestMethod")
@@ -2535,6 +2542,14 @@ class PluginSummary(BaseModel):
source: PluginSource
class PluginUninstallParams(BaseModel):
plugin_id: str = Field(..., alias="pluginId")
class PluginUninstallResponse(BaseModel):
pass
class ProductSurface(StrEnum):
chatgpt = "chatgpt"
codex = "codex"
@@ -4796,6 +4811,14 @@ class PluginInstallClientRequest(BaseModel):
params: PluginInstallParams
class PluginUninstallClientRequest(BaseModel):
id: RequestId
method: PluginUninstallClientRequestMethod = Field(
..., title="Plugin/uninstallClientRequestMethod"
)
params: PluginUninstallParams
class TurnInterruptClientRequest(BaseModel):
id: RequestId
method: TurnInterruptClientRequestMethod = Field(
@@ -6994,6 +7017,7 @@ class ClientRequest(
| AppListClientRequest
| SkillsConfigWriteClientRequest
| PluginInstallClientRequest
| PluginUninstallClientRequest
| TurnStartClientRequest
| TurnSteerClientRequest
| TurnInterruptClientRequest
@@ -7045,6 +7069,7 @@ class ClientRequest(
| AppListClientRequest
| SkillsConfigWriteClientRequest
| PluginInstallClientRequest
| PluginUninstallClientRequest
| TurnStartClientRequest
| TurnSteerClientRequest
| TurnInterruptClientRequest
+40 -48
View File
@@ -334,7 +334,7 @@ pub fn generate_python_with_options(
}
fn generate_python_models(schema_path: &Path, output_path: &Path) -> Result<()> {
let mut command = datamodel_codegen_command();
let mut command = datamodel_codegen_command()?;
command
.arg("--input")
.arg(schema_path)
@@ -370,7 +370,7 @@ fn generate_python_models(schema_path: &Path, output_path: &Path) -> Result<()>
if !status.success() {
return Err(anyhow!(
"Python model generator failed with status {status}. \
Install `uv`/`uvx` or `datamodel-codegen` to regenerate app-server Python bindings."
Install `uv` to run the vendored Python codegen toolchain."
));
}
@@ -385,33 +385,28 @@ Install `uv`/`uvx` or `datamodel-codegen` to regenerate app-server Python bindin
Ok(())
}
fn datamodel_codegen_command() -> Command {
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("run")
.arg("--project")
.arg(python_codegen_project)
.arg("--locked")
.arg("python")
.arg("-m")
.arg("datamodel_code_generator");
return command;
fn datamodel_codegen_command() -> Result<Command> {
// 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");
if !uv_exists() {
return Err(anyhow!(
"Python model generation requires `uv` to be installed so the vendored \
Python codegen toolchain can run from {}.",
python_codegen_project.display()
));
}
if command_exists("uvx") {
let mut command = Command::new("uvx");
command
.arg("--from")
.arg("datamodel-code-generator")
.arg("datamodel-codegen");
return command;
}
Command::new("datamodel-codegen")
let mut command = Command::new("uv");
command
.arg("run")
.arg("--project")
.arg(python_codegen_project)
.arg("--locked")
.arg("python")
.arg("-m")
.arg("datamodel_code_generator");
Ok(command)
}
fn run_ruff_on_python_files(ruff: Option<&Path>, out_dir: &Path) -> Result<()> {
@@ -438,39 +433,36 @@ fn ruff_command(ruff: Option<&Path>) -> Result<Command> {
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);
let python_codegen_project = Path::new(env!("CARGO_MANIFEST_DIR")).join("python");
if !uv_exists() {
return Err(anyhow!(
"Ruff formatting requires `uv` to be installed so the vendored Python \
codegen toolchain can run from {}, or pass `--ruff /path/to/ruff`.",
python_codegen_project.display()
));
}
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`."
))
let mut command = Command::new("uv");
command
.arg("run")
.arg("--project")
.arg(python_codegen_project)
.arg("--locked")
.arg("ruff");
Ok(command)
}
fn command_exists(command: &str) -> bool {
fn uv_exists() -> bool {
std::env::var_os("PATH").is_some_and(|paths| {
std::env::split_paths(&paths).any(|path| {
let candidate = path.join(command);
let candidate = path.join("uv");
if candidate.is_file() {
return true;
}
#[cfg(windows)]
{
let candidate = path.join(format!("{command}.exe"));
let candidate = path.join("uv.exe");
candidate.is_file()
}