pyo3_object_store/
local.rs1use std::fs::create_dir_all;
2use std::sync::Arc;
3
4use object_store::local::LocalFileSystem;
5use object_store::ObjectStoreScheme;
6use pyo3::exceptions::PyValueError;
7use pyo3::prelude::*;
8use pyo3::types::{PyDict, PyTuple, PyType};
9use pyo3::{intern, IntoPyObjectExt};
10
11use crate::error::PyObjectStoreResult;
12use crate::PyUrl;
13
14#[derive(Clone, Debug, PartialEq)]
15struct LocalConfig {
16 prefix: Option<std::path::PathBuf>,
17 automatic_cleanup: bool,
18 mkdir: bool,
19}
20
21impl LocalConfig {
22 fn __getnewargs_ex__<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
23 let args = PyTuple::new(py, vec![self.prefix.clone()])?.into_bound_py_any(py)?;
24 let kwargs = PyDict::new(py);
25 kwargs.set_item(intern!(py, "automatic_cleanup"), self.automatic_cleanup)?;
26 kwargs.set_item(intern!(py, "mkdir"), self.mkdir)?;
27 PyTuple::new(py, [args, kwargs.into_bound_py_any(py)?])
28 }
29}
30
31#[derive(Debug, Clone)]
33#[pyclass(name = "LocalStore", frozen, subclass)]
34pub struct PyLocalStore {
35 store: Arc<LocalFileSystem>,
36 config: LocalConfig,
37}
38
39impl AsRef<Arc<LocalFileSystem>> for PyLocalStore {
40 fn as_ref(&self) -> &Arc<LocalFileSystem> {
41 &self.store
42 }
43}
44
45impl PyLocalStore {
46 pub fn into_inner(self) -> Arc<LocalFileSystem> {
48 self.store
49 }
50}
51
52#[pymethods]
53impl PyLocalStore {
54 #[new]
55 #[pyo3(signature = (prefix=None, *, automatic_cleanup=false, mkdir=false))]
56 fn new(
57 prefix: Option<std::path::PathBuf>,
58 automatic_cleanup: bool,
59 mkdir: bool,
60 ) -> PyObjectStoreResult<Self> {
61 let fs = if let Some(prefix) = &prefix {
62 if mkdir {
63 create_dir_all(prefix)?;
64 }
65 LocalFileSystem::new_with_prefix(prefix)?
66 } else {
67 LocalFileSystem::new()
68 };
69 let fs = fs.with_automatic_cleanup(automatic_cleanup);
70 Ok(Self {
71 store: Arc::new(fs),
72 config: LocalConfig {
73 prefix,
74 automatic_cleanup,
75 mkdir,
76 },
77 })
78 }
79
80 #[classmethod]
81 #[pyo3(signature = (url, *, automatic_cleanup=false, mkdir=false))]
82 pub(crate) fn from_url<'py>(
83 cls: &Bound<'py, PyType>,
84 url: PyUrl,
85 automatic_cleanup: bool,
86 mkdir: bool,
87 ) -> PyObjectStoreResult<Bound<'py, PyAny>> {
88 let url = url.into_inner();
89 let (scheme, path) = ObjectStoreScheme::parse(&url).map_err(object_store::Error::from)?;
90
91 if !matches!(scheme, ObjectStoreScheme::Local) {
92 return Err(PyValueError::new_err("Not a `file://` URL").into());
93 }
94
95 let root = std::path::Path::new("/");
99 let full_path = root.join(path.as_ref());
100
101 let kwargs = PyDict::new(cls.py());
104 kwargs.set_item("prefix", full_path)?;
105 kwargs.set_item("automatic_cleanup", automatic_cleanup)?;
106 kwargs.set_item("mkdir", mkdir)?;
107 Ok(cls.call((), Some(&kwargs))?)
108 }
109
110 fn __eq__(&self, other: &Bound<PyAny>) -> bool {
111 other
114 .cast::<PyLocalStore>()
115 .map(|other| self.config == other.get().config)
116 .unwrap_or(false)
117 }
118
119 fn __getnewargs_ex__<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
120 self.config.__getnewargs_ex__(py)
121 }
122
123 fn __repr__(&self) -> String {
124 if let Some(prefix) = &self.config.prefix {
125 format!("LocalStore(\"{}\")", prefix.display())
126 } else {
127 "LocalStore".to_string()
128 }
129 }
130
131 #[getter]
132 fn prefix<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
133 if let Some(prefix) = &self.config.prefix {
137 let pathlib_mod = py.import(intern!(py, "pathlib"))?;
138 pathlib_mod.call_method1(intern!(py, "Path"), PyTuple::new(py, vec![prefix])?)
139 } else {
140 py.None().into_bound_py_any(py)
141 }
142 }
143}