ossctl_core/release/adapters/
python.rs1use std::time::Duration;
9
10use crate::contract::schema::Adapter;
11use crate::protocol::release::{BuildArtifacts, DryRunReport, PlannedCommand, PublishReceipt};
12
13use super::{make_receipt, run_all, AdapterError, AdapterTarget, EffectCtx, ReleaseAdapter};
14
15pub struct PythonAdapter {
17 adapter: Adapter,
18}
19
20impl PythonAdapter {
21 #[must_use]
23 pub fn new(adapter: Adapter) -> Self {
24 debug_assert!(matches!(
25 adapter,
26 Adapter::GhActionPypiPublish | Adapter::Twine
27 ));
28 Self { adapter }
29 }
30}
31
32impl ReleaseAdapter for PythonAdapter {
33 fn adapter(&self) -> Adapter {
34 self.adapter
35 }
36
37 fn is_ci_delegated(&self) -> bool {
38 matches!(self.adapter, Adapter::GhActionPypiPublish)
43 }
44
45 fn dry_run(
46 &self,
47 _ctx: &EffectCtx<'_>,
48 _t: &AdapterTarget,
49 ) -> Result<DryRunReport, AdapterError> {
50 let notes = match self.adapter {
51 Adapter::GhActionPypiPublish => {
52 vec![
53 "upload runs in CI via the PyPI trusted publisher, not from this host"
54 .to_string(),
55 ]
56 }
57 _ => vec![],
58 };
59 Ok(DryRunReport {
61 adapter: self.adapter,
62 planned_commands: vec![PlannedCommand::new("twine", &["check", "dist/*"])],
63 notes,
64 })
65 }
66
67 fn build(
68 &self,
69 ctx: &EffectCtx<'_>,
70 t: &AdapterTarget,
71 ) -> Result<BuildArtifacts, AdapterError> {
72 run_all(ctx, &[PlannedCommand::new("python", &["-m", "build"])])?;
73 Ok(BuildArtifacts {
75 adapter: self.adapter,
76 artifacts: vec![
77 format!("{}-{}-py3-none-any.whl", t.package, t.version),
78 format!("{}-{}.tar.gz", t.package, t.version),
79 ],
80 notes: vec![],
81 })
82 }
83
84 fn publish(
85 &self,
86 ctx: &EffectCtx<'_>,
87 t: &AdapterTarget,
88 ) -> Result<PublishReceipt, AdapterError> {
89 if matches!(self.adapter, Adapter::GhActionPypiPublish) {
91 return Err(AdapterError::Unsupported {
95 adapter: self.adapter,
96 operation: "publish",
97 });
98 }
99 run_all(ctx, &[PlannedCommand::new("twine", &["upload", "dist/*"])])?;
100 let remote_url = Some(format!(
101 "https://pypi.org/project/{}/{}/",
102 t.package, t.version
103 ));
104 Ok(make_receipt(ctx, t, None, remote_url))
105 }
106
107 fn timeout(&self) -> Duration {
108 Duration::from_secs(600)
109 }
110}