use std::time::Duration;
use crate::contract::schema::Adapter;
use crate::protocol::release::{BuildArtifacts, DryRunReport, PlannedCommand, PublishReceipt};
use super::{make_receipt, run_all, AdapterError, AdapterTarget, EffectCtx, ReleaseAdapter};
pub struct PythonAdapter {
adapter: Adapter,
}
impl PythonAdapter {
#[must_use]
pub fn new(adapter: Adapter) -> Self {
debug_assert!(matches!(
adapter,
Adapter::GhActionPypiPublish | Adapter::Twine
));
Self { adapter }
}
}
impl ReleaseAdapter for PythonAdapter {
fn adapter(&self) -> Adapter {
self.adapter
}
fn is_ci_delegated(&self) -> bool {
matches!(self.adapter, Adapter::GhActionPypiPublish)
}
fn dry_run(
&self,
_ctx: &EffectCtx<'_>,
_t: &AdapterTarget,
) -> Result<DryRunReport, AdapterError> {
let notes = match self.adapter {
Adapter::GhActionPypiPublish => {
vec![
"upload runs in CI via the PyPI trusted publisher, not from this host"
.to_string(),
]
}
_ => vec![],
};
Ok(DryRunReport {
adapter: self.adapter,
planned_commands: vec![PlannedCommand::new("twine", &["check", "dist/*"])],
notes,
})
}
fn build(
&self,
ctx: &EffectCtx<'_>,
t: &AdapterTarget,
) -> Result<BuildArtifacts, AdapterError> {
run_all(ctx, &[PlannedCommand::new("python", &["-m", "build"])])?;
Ok(BuildArtifacts {
adapter: self.adapter,
artifacts: vec![
format!("{}-{}-py3-none-any.whl", t.package, t.version),
format!("{}-{}.tar.gz", t.package, t.version),
],
notes: vec![],
})
}
fn publish(
&self,
ctx: &EffectCtx<'_>,
t: &AdapterTarget,
) -> Result<PublishReceipt, AdapterError> {
if matches!(self.adapter, Adapter::GhActionPypiPublish) {
return Err(AdapterError::Unsupported {
adapter: self.adapter,
operation: "publish",
});
}
run_all(ctx, &[PlannedCommand::new("twine", &["upload", "dist/*"])])?;
let remote_url = Some(format!(
"https://pypi.org/project/{}/{}/",
t.package, t.version
));
Ok(make_receipt(ctx, t, None, remote_url))
}
fn timeout(&self) -> Duration {
Duration::from_secs(600)
}
}