1#![allow(unused_qualifications)]
2#![allow(non_local_definitions, reason = "necessary for pyo3::pymethods")]
3
4use pyo3::{
5 exceptions::PyValueError,
6 prelude::*,
7 types::{PyFunction, PyString},
8};
9use rigetti_pyo3::{create_init_submodule, impl_repr, py_function_sync_async, sync::Awaitable};
10use tokio_util::sync::CancellationToken;
11
12#[cfg(feature = "stubs")]
13use pyo3_stub_gen::derive::{gen_stub_pyfunction, gen_stub_pymethods};
14
15use crate::configuration::{
16 secrets::{DEFAULT_SECRETS_PATH, SECRETS_PATH_VAR},
17 settings::{DEFAULT_SETTINGS_PATH, SETTINGS_PATH_VAR},
18 ClientConfigurationBuilderError, API_URL_VAR, DEFAULT_API_URL, DEFAULT_GRPC_API_URL,
19 DEFAULT_PROFILE_NAME, DEFAULT_QUILC_URL, DEFAULT_QVM_URL, GRPC_API_URL_VAR, PROFILE_NAME_VAR,
20 QUILC_URL_VAR, QVM_URL_VAR,
21};
22use crate::errors;
23
24use super::{
25 error::TokenError,
26 secrets::{SecretAccessToken, SecretRefreshToken},
27 settings::AuthServer,
28 tokens::{ClientCredentials, ClientSecret, ExternallyManaged, PkceFlow},
29 ClientConfiguration, ClientConfigurationBuilder, LoadError, OAuthGrant, OAuthSession,
30 RefreshToken, TokenDispatcher,
31};
32
33create_init_submodule! {
34 classes: [
35 ClientConfiguration,
36 ClientConfigurationBuilder,
37 AuthServer,
38 OAuthSession,
39 RefreshToken,
40 ClientCredentials,
41 ClientSecret,
42 ExternallyManaged,
43 PkceFlow,
44 SecretAccessToken,
45 SecretRefreshToken,
46 TokenDispatcher
47 ],
48
49 consts: [
50 API_URL_VAR,
51 DEFAULT_API_URL,
52 DEFAULT_GRPC_API_URL,
53 DEFAULT_PROFILE_NAME,
54 DEFAULT_QUILC_URL,
55 DEFAULT_QVM_URL,
56 DEFAULT_SECRETS_PATH,
57 DEFAULT_SETTINGS_PATH,
58 GRPC_API_URL_VAR,
59 PROFILE_NAME_VAR,
60 QUILC_URL_VAR,
61 QVM_URL_VAR,
62 SECRETS_PATH_VAR,
63 SETTINGS_PATH_VAR
64 ],
65
66 errors: [
67 errors::ClientConfigurationBuilderError,
68 errors::ConfigurationError,
69 errors::LoadError,
70 errors::TokenError
71 ],
72
73 funcs: [
74 py_get_oauth_session,
75 py_get_oauth_session_async,
76 py_get_bearer_access_token,
77 py_get_bearer_access_token_async,
78 py_request_access_token,
79 py_request_access_token_async
80 ],
81
82}
83
84#[cfg(feature = "stubs")]
85#[derive(IntoPyObject)]
86struct Final<T>(T);
87
88#[cfg(feature = "stubs")]
89impl<T> pyo3_stub_gen::PyStubType for Final<T> {
90 fn type_output() -> pyo3_stub_gen::TypeInfo {
91 pyo3_stub_gen::TypeInfo::with_module("typing.Final", "typing".into())
92 }
93}
94
95macro_rules! stub_consts {
97 ( $($name:ident),* ) => {
98 $(
99 #[cfg(feature = "stubs")]
100 ::pyo3_stub_gen::module_variable!(
101 "qcs_api_client_common.configuration",
102 stringify!($name),
103 Final<&str>,
104 Final($name)
105 );
106 )*
107 };
108}
109
110stub_consts!(
111 API_URL_VAR,
112 DEFAULT_API_URL,
113 DEFAULT_GRPC_API_URL,
114 DEFAULT_PROFILE_NAME,
115 DEFAULT_QUILC_URL,
116 DEFAULT_QVM_URL,
117 DEFAULT_SECRETS_PATH,
118 DEFAULT_SETTINGS_PATH,
119 GRPC_API_URL_VAR,
120 PROFILE_NAME_VAR,
121 QUILC_URL_VAR,
122 QVM_URL_VAR,
123 SECRETS_PATH_VAR,
124 SETTINGS_PATH_VAR
125);
126
127impl FromPyObject<'_, '_> for SecretRefreshToken {
132 type Error = PyErr;
133
134 fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
135 if let Ok(token) = obj.cast::<PyString>() {
136 Ok(Self::__new__(token.extract()?))
137 } else if let Ok(token) = obj.cast::<RefreshToken>() {
138 Ok(token.borrow().refresh_token.clone())
139 } else if let Ok(token) = obj.cast::<Self>() {
140 Ok(token.borrow().clone())
141 } else {
142 Err(PyValueError::new_err(
143 "expected str | SecretRefreshToken | RefreshToken",
144 ))
145 }
146 }
147}
148
149impl FromPyObject<'_, '_> for SecretAccessToken {
150 type Error = PyErr;
151
152 fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
153 if let Ok(token) = obj.cast::<PyString>() {
154 Ok(Self::__new__(token.extract()?))
155 } else if let Ok(token) = obj.cast::<Self>() {
156 Ok(token.borrow().clone())
157 } else {
158 Err(PyValueError::new_err("expected str | SecretAccessToken"))
159 }
160 }
161}
162
163impl FromPyObject<'_, '_> for ClientSecret {
164 type Error = PyErr;
165
166 fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
167 if let Ok(token) = obj.cast::<PyString>() {
168 Ok(Self::__new__(token.extract()?))
169 } else if let Ok(token) = obj.cast::<Self>() {
170 Ok(token.borrow().clone())
171 } else {
172 Err(PyValueError::new_err("expected str | ClientSecret"))
173 }
174 }
175}
176
177impl_repr!(RefreshToken);
178
179#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
180#[pymethods]
181impl RefreshToken {
182 #[new]
183 const fn __new__(refresh_token: SecretRefreshToken) -> Self {
184 Self::new(refresh_token)
185 }
186}
187
188impl_repr!(ClientCredentials);
189
190#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
191#[pymethods]
192impl ClientCredentials {
193 #[new]
194 fn __new__(client_id: String, client_secret: String) -> Self {
195 Self::new(client_id, ClientSecret::from(client_secret))
196 }
197}
198
199impl_repr!(ExternallyManaged);
200
201#[cfg_attr(not(feature = "stubs"), optipy::strip_pyo3(only_stubs))]
202#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
203#[pymethods]
204impl ExternallyManaged {
205 #[new]
206 fn __new__(
207 #[gen_stub(
208 override_type(
209 type_repr="collections.abc.Callable[[AuthServer], str]",
210 imports=("collections.abc")
211 )
212 )]
213 refresh_function: Py<PyFunction>,
214 ) -> Self {
215 #[allow(trivial_casts)] let refresh_closure = move |auth_server: AuthServer| {
219 let refresh_function = Python::attach(|py| refresh_function.clone_ref(py));
220 Box::pin(async move {
221 Python::attach(|py| {
222 let result = refresh_function.call1(py, (auth_server,));
223 match result {
224 Ok(value) => value
225 .extract::<String>(py)
226 .map_or_else(|_| panic!("ExternallyManaged refresh function returned an unexpected type. Expected a string, got {value:?}"), Ok),
227 Err(err) => Err(Box::<dyn std::error::Error + Send + Sync>::from(err))
228 }
229 })
230 }) as super::tokens::RefreshResult
231 };
232
233 Self::new(refresh_closure)
234 }
235}
236
237impl_repr!(PkceFlow);
238
239#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
240#[pymethods]
241impl PkceFlow {
242 #[new]
243 fn __new__(py: Python<'_>, auth_server: AuthServer) -> PyResult<Self> {
244 pyo3_async_runtimes::tokio::run(py, async move {
245 let cancel_token = cancel_token_with_ctrl_c();
246 Self::new_login_flow(cancel_token, &auth_server)
247 .await
248 .map_err(|err| LoadError::from(err).into())
249 })
250 }
251}
252
253#[cfg(feature = "stubs")]
254pyo3_stub_gen::impl_stub_type!(
255 OAuthGrant = RefreshToken | ClientConfiguration | ExternallyManaged | PkceFlow
256);
257
258impl_repr!(OAuthSession);
259
260#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
261#[pymethods]
262impl OAuthSession {
263 #[new]
264 #[pyo3(signature = (payload, auth_server, access_token = None))]
265 const fn __new__(
266 payload: OAuthGrant,
267 auth_server: AuthServer,
268 access_token: Option<SecretAccessToken>,
269 ) -> Self {
270 Self::new(payload, auth_server, access_token)
271 }
272
273 #[pyo3(name = "validate")]
274 fn py_validate(&self) -> Result<SecretAccessToken, TokenError> {
275 self.validate()
276 }
277
278 #[pyo3(name = "request_access_token")]
279 fn py_request_access_token(&self, py: Python<'_>) -> PyResult<SecretAccessToken> {
280 py_request_access_token(py, self.clone())
281 }
282
283 #[pyo3(name = "request_access_token_async")]
284 fn py_request_access_token_async<'py>(
285 &self,
286 py: Python<'py>,
287 ) -> PyResult<Awaitable<'py, SecretAccessToken>> {
288 py_request_access_token_async(py, self.clone())
289 }
290}
291
292py_function_sync_async! {
293 #[cfg_attr(feature = "stubs", gen_stub_pyfunction(module = "qcs_api_client_common.configuration"))]
294 #[pyfunction]
295 async fn get_oauth_session(tokens: Option<TokenDispatcher>) -> PyResult<OAuthSession> {
296 Ok(tokens.ok_or(TokenError::NoRefreshToken)?.tokens().await)
297 }
298}
299
300py_function_sync_async! {
301 #[cfg_attr(feature = "stubs", gen_stub_pyfunction(module = "qcs_api_client_common.configuration"))]
302 #[pyfunction]
303 async fn get_bearer_access_token(configuration: ClientConfiguration) -> PyResult<SecretAccessToken> {
304 configuration.get_bearer_access_token().await.map_err(PyErr::from)
305 }
306}
307
308py_function_sync_async! {
309 #[cfg_attr(feature = "stubs", gen_stub_pyfunction(module = "qcs_api_client_common.configuration"))]
310 #[pyfunction]
311 async fn request_access_token(session: OAuthSession) -> PyResult<SecretAccessToken> {
312 session.clone().request_access_token().await.cloned().map_err(PyErr::from)
313 }
314}
315
316impl_repr!(ClientConfiguration);
317
318#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
319#[pymethods]
320impl ClientConfiguration {
321 #[new]
322 #[pyo3(signature = (
323 api_url = None, grpc_api_url = None, quilc_url = None, qvm_url = None,
324 oauth_session = None,
325 ))]
326 fn __new__(
327 api_url: Option<String>,
328 grpc_api_url: Option<String>,
329 quilc_url: Option<String>,
330 qvm_url: Option<String>,
331 oauth_session: Option<OAuthSession>,
332 ) -> Self {
333 let mut builder = ClientConfigurationBuilder::default();
334
335 if let Some(api_url) = api_url {
336 builder.api_url(api_url);
337 }
338
339 if let Some(grpc_api_url) = grpc_api_url {
340 builder.grpc_api_url(grpc_api_url);
341 }
342
343 if let Some(quilc_url) = quilc_url {
344 builder.quilc_url(quilc_url);
345 }
346
347 if let Some(qvm_url) = qvm_url {
348 builder.qvm_url(qvm_url);
349 }
350
351 builder.oauth_session(oauth_session);
352
353 builder
354 .build()
355 .expect("our builder is valid regardless of which URLs are set")
356 }
357
358 #[staticmethod]
359 #[pyo3(name = "load_default")]
360 fn py_load_default(_py: Python<'_>) -> Result<Self, LoadError> {
361 Self::load_default()
362 }
363
364 #[staticmethod]
365 #[pyo3(name = "load_default_with_login")]
366 fn py_load_default_with_login(py: Python<'_>) -> PyResult<Self> {
367 pyo3_async_runtimes::tokio::run(py, async move {
368 let cancel_token = cancel_token_with_ctrl_c();
369 Self::load_with_login(cancel_token, None)
370 .await
371 .map_err(Into::into)
372 })
373 }
374
375 #[staticmethod]
376 #[pyo3(name = "builder")]
377 fn py_builder() -> ClientConfigurationBuilder {
378 ClientConfigurationBuilder::default()
379 }
380
381 #[staticmethod]
382 #[pyo3(name = "load_profile")]
383 fn py_load_profile(_py: Python<'_>, profile_name: String) -> Result<Self, LoadError> {
384 Self::load_profile(profile_name)
385 }
386
387 #[pyo3(name = "get_bearer_access_token")]
388 fn py_get_bearer_access_token(&self, py: Python<'_>) -> PyResult<SecretAccessToken> {
389 py_get_bearer_access_token(py, self.clone())
390 }
391
392 #[pyo3(name = "get_bearer_access_token_async")]
393 fn py_get_bearer_access_token_async<'py>(
394 &self,
395 py: Python<'py>,
396 ) -> PyResult<Awaitable<'py, SecretAccessToken>> {
397 py_get_bearer_access_token_async(py, self.clone())
398 }
399
400 pub fn get_oauth_session(&self, py: Python<'_>) -> PyResult<OAuthSession> {
406 py_get_oauth_session(py, self.oauth_session.clone())
407 }
408
409 #[allow(clippy::needless_pass_by_value)] fn get_oauth_session_async<'py>(
411 self_: PyRefMut<'py, Self>,
412 py: Python<'py>,
413 ) -> PyResult<Awaitable<'py, OAuthSession>> {
414 py_get_oauth_session_async(py, self_.oauth_session.clone())
415 }
416}
417
418#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
419#[pymethods]
420impl ClientConfigurationBuilder {
421 #[new]
422 fn __new__() -> Self {
423 Self::default()
424 }
425
426 #[setter]
431 fn set_oauth_session(&mut self, oauth_session: Option<OAuthSession>) {
432 self.oauth_session = Some(oauth_session.map(Into::into));
433 }
434
435 #[pyo3(name = "build")]
436 fn py_build(&self) -> Result<ClientConfiguration, ClientConfigurationBuilderError> {
437 self.build()
438 }
439}
440
441impl_repr!(AuthServer);
442
443#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
444#[pymethods]
445impl AuthServer {
446 #[new]
447 #[pyo3(signature = (client_id, issuer, scopes = None))]
448 const fn __new__(client_id: String, issuer: String, scopes: Option<Vec<String>>) -> Self {
449 Self::new(client_id, issuer, scopes)
450 }
451
452 #[staticmethod]
453 #[pyo3(name = "default")]
454 fn py_default() -> Self {
455 Self::default()
456 }
457}
458
459fn cancel_token_with_ctrl_c() -> CancellationToken {
460 let cancel_token = CancellationToken::new();
461 let cancel_token_ctrl_c = cancel_token.clone();
462 tokio::spawn(cancel_token.clone().run_until_cancelled_owned(async move {
463 match tokio::signal::ctrl_c().await {
464 Ok(()) => cancel_token_ctrl_c.cancel(),
465 Err(error) => eprintln!("Failed to register signal handler: {error}"),
466 }
467 }));
468 cancel_token
469}