polars_python/io/
cloud_options.rs1use std::time::Duration;
2
3use polars::prelude::CloudScheme;
4use polars_core::config::verbose_print_sensitive;
5use polars_io::cloud::{CloudOptions, CloudRetryConfig};
6use polars_utils::total_ord::TotalOrdWrap;
7use pyo3::exceptions::PyValueError;
8use pyo3::intern;
9use pyo3::prelude::*;
10use pyo3::pybacked::PyBackedStr;
11use pyo3::types::PyDict;
12
13use crate::utils::to_py_err;
14
15pub struct OptPyCloudOptions<'py>(Bound<'py, PyAny>);
17
18impl<'a, 'py> FromPyObject<'a, 'py> for OptPyCloudOptions<'py> {
19 type Error = PyErr;
20
21 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
22 Ok(Self(ob.to_owned()))
23 }
24}
25
26impl OptPyCloudOptions<'_> {
27 pub fn extract_opt_cloud_options(
28 &self,
29 cloud_scheme: Option<CloudScheme>,
30 credential_provider: Option<Py<PyAny>>,
31 ) -> PyResult<Option<CloudOptions>> {
32 let py = self.0.py();
33
34 let mut storage_options: Vec<(PyBackedStr, String)> = vec![];
35 let mut file_cache_ttl: u64 = 2;
36 let mut retry_config = CloudRetryConfig::default();
37
38 let storage_options_dict: Option<Bound<'_, PyDict>> = self.0.extract()?;
39
40 if let Some(storage_options_dict) = storage_options_dict {
41 storage_options.reserve(
42 storage_options_dict
43 .call_method0(intern!(py, "__len__"))?
44 .extract()?,
45 );
46
47 for v in storage_options_dict
48 .call_method0(intern!(py, "items"))?
49 .try_iter()?
50 {
51 let (key, value): (PyBackedStr, Bound<'_, PyAny>) = v?.extract()?;
52
53 macro_rules! expected_type {
54 ($key_name:expr, $type_name:expr) => {{
55 |_| {
56 let key_name = $key_name;
57 let type_name = $type_name;
58 PyValueError::new_err(format!(
59 "invalid value for '{key_name}': '{value}' (expected {type_name})"
60 ))
61 }
62 }};
63 }
64
65 match &*key {
66 "file_cache_ttl" => {
67 file_cache_ttl = value
68 .extract()
69 .map_err(expected_type!("file_cache_ttl", "int"))?;
70 },
71 "max_retries" => {
72 retry_config.max_retries = value
73 .extract()
74 .map_err(expected_type!("max_retries", "int"))?;
75 },
76 "retry_timeout_ms" => {
77 retry_config.retry_timeout = Some(Duration::from_millis(
78 value
79 .extract()
80 .map_err(expected_type!("retry_timeout", "int"))?,
81 ));
82 },
83 "retry_init_backoff_ms" => {
84 retry_config.retry_init_backoff = Some(Duration::from_millis(
85 value
86 .extract()
87 .map_err(expected_type!("retry_init_backoff", "int"))?,
88 ));
89 },
90 "retry_max_backoff_ms" => {
91 retry_config.retry_max_backoff = Some(Duration::from_millis(
92 value
93 .extract()
94 .map_err(expected_type!("retry_max_backoff", "int"))?,
95 ));
96 },
97 "retry_base_multiplier" => {
98 retry_config.retry_base_multiplier = Some(TotalOrdWrap(
99 value
100 .extract()
101 .map_err(expected_type!("retry_base_multiplier", "float"))?,
102 ));
103 },
104 _ => {
105 let value: String = value.extract().map_err(expected_type!(&key, "str"))?;
106 storage_options.push((key, value))
107 },
108 }
109 }
110 }
111
112 let cloud_options = CloudOptions::from_untyped_config(cloud_scheme, storage_options)
113 .map_err(to_py_err)?
114 .with_retry_config(retry_config);
115
116 #[cfg(feature = "cloud")]
117 let mut cloud_options =
118 cloud_options.with_credential_provider(credential_provider.map(
119 polars_io::cloud::credential_provider::PlCredentialProvider::from_python_builder,
120 ));
121
122 #[cfg(feature = "cloud")]
123 if file_cache_ttl > 0 {
124 cloud_options.file_cache_ttl = file_cache_ttl;
125 }
126 verbose_print_sensitive(|| format!("extracted cloud_options: {:?}", cloud_options));
127
128 Ok(Some(cloud_options))
129 }
130}