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 dry_run(
38 &self,
39 _ctx: &EffectCtx<'_>,
40 _t: &AdapterTarget,
41 ) -> Result<DryRunReport, AdapterError> {
42 let notes = match self.adapter {
43 Adapter::GhActionPypiPublish => {
44 vec![
45 "upload runs in CI via the PyPI trusted publisher, not from this host"
46 .to_string(),
47 ]
48 }
49 _ => vec![],
50 };
51 Ok(DryRunReport {
53 adapter: self.adapter,
54 planned_commands: vec![PlannedCommand::new("twine", &["check", "dist/*"])],
55 notes,
56 })
57 }
58
59 fn build(
60 &self,
61 ctx: &EffectCtx<'_>,
62 t: &AdapterTarget,
63 ) -> Result<BuildArtifacts, AdapterError> {
64 run_all(ctx, &[PlannedCommand::new("python", &["-m", "build"])])?;
65 Ok(BuildArtifacts {
67 adapter: self.adapter,
68 artifacts: vec![
69 format!("{}-{}-py3-none-any.whl", t.package, t.version),
70 format!("{}-{}.tar.gz", t.package, t.version),
71 ],
72 notes: vec![],
73 })
74 }
75
76 fn publish(
77 &self,
78 ctx: &EffectCtx<'_>,
79 t: &AdapterTarget,
80 ) -> Result<PublishReceipt, AdapterError> {
81 if matches!(self.adapter, Adapter::GhActionPypiPublish) {
83 return Err(AdapterError::Unsupported {
87 adapter: self.adapter,
88 operation: "publish",
89 });
90 }
91 run_all(ctx, &[PlannedCommand::new("twine", &["upload", "dist/*"])])?;
92 let remote_url = Some(format!(
93 "https://pypi.org/project/{}/{}/",
94 t.package, t.version
95 ));
96 Ok(make_receipt(ctx, t, None, remote_url))
97 }
98
99 fn timeout(&self) -> Duration {
100 Duration::from_secs(600)
101 }
102}