From c3db0bc257b9aed38ba7836bf8e5ac741d9b328a Mon Sep 17 00:00:00 2001 From: jessekrubin Date: Mon, 10 Mar 2025 13:32:46 -0700 Subject: [PATCH 1/2] frozen python structs --- CHANGELOG.md | 1 + crates/utiles/src/mbt/stream_writer.rs | 1 - utiles-pyo3/src/pyutiles/pybbox.rs | 12 +++---- utiles-pyo3/src/pyutiles/pyfns.rs | 14 ++++---- utiles-pyo3/src/pyutiles/pylnglat.rs | 6 ++-- utiles-pyo3/src/pyutiles/pylnglatbbox.rs | 6 ++-- utiles-pyo3/src/pyutiles/pyparsing.rs | 16 ++++----- utiles-pyo3/src/pyutiles/pytile.rs | 46 +++++++++--------------- utiles-pyo3/src/pyutiles/pytile_fmts.rs | 4 +-- utiles-pyo3/src/pyutiles/pytile_type.rs | 11 ++---- utiles-pyo3/tests/test_tile.py | 8 +++++ 11 files changed, 56 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d517fb86..761e579c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - useless conversions - const functions - pyo3 v0.24.x +- frozen py structs --- diff --git a/crates/utiles/src/mbt/stream_writer.rs b/crates/utiles/src/mbt/stream_writer.rs index 35e23887..266a46a2 100644 --- a/crates/utiles/src/mbt/stream_writer.rs +++ b/crates/utiles/src/mbt/stream_writer.rs @@ -170,7 +170,6 @@ impl MbtStreamWriterSync { pub async fn write(&mut self) -> UtilesResult<()> { let db_type = self.mbt.query_mbt_type()?; self.preflight()?; - // let db_type = MbtType::Flat; match db_type { MbtType::Flat => self.write_flat().await, MbtType::Hash => self.write_hash().await, diff --git a/utiles-pyo3/src/pyutiles/pybbox.rs b/utiles-pyo3/src/pyutiles/pybbox.rs index e34824ac..431d3b08 100644 --- a/utiles-pyo3/src/pyutiles/pybbox.rs +++ b/utiles-pyo3/src/pyutiles/pybbox.rs @@ -6,7 +6,7 @@ use pyo3::types::PyType; use pyo3::{exceptions, pyclass, pymethods, PyAny, PyErr, PyRef, PyResult, Python}; use utiles::BBox; -#[pyclass(name = "Bbox", module = "utiles._utiles")] +#[pyclass(name = "Bbox", module = "utiles._utiles", frozen)] #[derive(Clone)] pub struct PyBbox { pub bbox: BBox, @@ -15,7 +15,7 @@ pub struct PyBbox { #[pymethods] impl PyBbox { #[new] - pub fn new(left: f64, bottom: f64, right: f64, top: f64) -> Self { + pub fn py_new(left: f64, bottom: f64, right: f64, top: f64) -> Self { PyBbox { bbox: BBox { west: left, @@ -30,10 +30,10 @@ impl PyBbox { pub fn from_tile(_cls: &Bound<'_, PyType>, tile: &PyTile) -> Self { let ul = utiles::ul(tile.xyz.x, tile.xyz.y, tile.xyz.z); let lr = utiles::lr(tile.xyz.x, tile.xyz.y, tile.xyz.z); - Self::new(ul.lng(), lr.lat(), lr.lng(), ul.lat()) + Self::py_new(ul.lng(), lr.lat(), lr.lng(), ul.lat()) } - pub fn __str__(&self) -> String { + pub fn __repr__(&self) -> String { format!( "Bbox(left={}, bottom={}, right={}, top={})", self.bbox.left(), @@ -43,8 +43,8 @@ impl PyBbox { ) } - pub fn __repr__(&self) -> String { - self.__str__() + pub fn __str__(&self) -> String { + self.__repr__() } #[getter] diff --git a/utiles-pyo3/src/pyutiles/pyfns.rs b/utiles-pyo3/src/pyutiles/pyfns.rs index f0e82451..486225d4 100644 --- a/utiles-pyo3/src/pyutiles/pyfns.rs +++ b/utiles-pyo3/src/pyutiles/pyfns.rs @@ -18,7 +18,7 @@ use crate::pyutiles::zoom::PyZoomOrZooms; #[pyfunction] pub fn xyz(x: u32, y: u32, z: u8) -> PyTile { - PyTile::new(x, y, z) + PyTile::py_new(x, y, z) } #[pyfunction] @@ -57,7 +57,7 @@ pub fn _xy(lng: f64, lat: f64, truncate: Option) -> PyResult<(f64, f64)> { #[pyo3(signature = (x, y, truncate = None))] pub fn lnglat(x: f64, y: f64, truncate: Option) -> PyLngLat { let lnglat = utiles::lnglat(x, y, truncate); - PyLngLat::new(lnglat.lng(), lnglat.lat()) + PyLngLat::py_new(lnglat.lng(), lnglat.lat()) } #[pyfunction] @@ -100,7 +100,7 @@ pub fn qk2xyz(quadkey: &str) -> PyResult { #[pyfunction] pub fn from_tuple(tile: TileTuple) -> PyTile { - PyTile::new(tile.0, tile.1, tile.2) + PyTile::py_new(tile.0, tile.1, tile.2) } #[pyfunction] #[pyo3(signature = (tile, fid = None, props = None, projected = None, buffer = None, precision = None) @@ -138,12 +138,12 @@ pub fn _extract(arg: &Bound<'_, PyAny>) -> PyResult> { } else if let Ok(seq) = arg.extract::>() { return Ok(seq .iter() - .map(|xyz| PyTile::new(xyz.0, xyz.1, xyz.2 as u8)) + .map(|xyz| PyTile::py_new(xyz.0, xyz.1, xyz.2 as u8)) .collect()); } else if let Ok(seq) = arg.extract::>>() { return Ok(seq .iter() - .map(|xyz| PyTile::new(xyz[0], xyz[1], xyz[2] as u8)) + .map(|xyz| PyTile::py_new(xyz[0], xyz[1], xyz[2] as u8)) .collect()); } Err(PyErr::new::( @@ -156,7 +156,7 @@ pub fn _extract(arg: &Bound<'_, PyAny>) -> PyResult> { pub fn xy_bounds(args: &Bound<'_, PyTuple>) -> PyResult { let tile = pyparsing::parse_tile_arg(args)?; let web_bbox = utiles::xyz2bbox(tile.xyz.x, tile.xyz.y, tile.xyz.z); - Ok(PyBbox::new( + Ok(PyBbox::py_new( web_bbox.left(), web_bbox.bottom(), web_bbox.right(), @@ -390,5 +390,5 @@ pub fn geojson_bounds(obj: &Bound<'_, PyAny>) -> PyResult { bbox.3.max(lat), ); } - Ok(PyLngLatBbox::new(bbox.0, bbox.1, bbox.2, bbox.3)) + Ok(PyLngLatBbox::py_new(bbox.0, bbox.1, bbox.2, bbox.3)) } diff --git a/utiles-pyo3/src/pyutiles/pylnglat.rs b/utiles-pyo3/src/pyutiles/pylnglat.rs index e628abcd..a011cff1 100644 --- a/utiles-pyo3/src/pyutiles/pylnglat.rs +++ b/utiles-pyo3/src/pyutiles/pylnglat.rs @@ -4,7 +4,7 @@ use pyo3::exceptions::{self}; use pyo3::prelude::*; use pyo3::types::PyType; -#[pyclass(name = "LngLat", module = "utiles._utiles")] +#[pyclass(name = "LngLat", module = "utiles._utiles", frozen)] pub struct PyLngLat { lnglat: utiles::LngLat, } @@ -12,7 +12,7 @@ pub struct PyLngLat { #[pymethods] impl PyLngLat { #[new] - pub fn new(lng: f64, lat: f64) -> Self { + pub fn py_new(lng: f64, lat: f64) -> Self { Self { lnglat: utiles::LngLat::new(lng, lat), } @@ -21,7 +21,7 @@ impl PyLngLat { #[classmethod] pub fn from_tile(_cls: &Bound<'_, PyType>, tile: &PyTile) -> Self { let ll = utiles::ul(tile.xyz.x, tile.xyz.y, tile.xyz.z); - Self::new(ll.lng(), ll.lat()) + Self::py_new(ll.lng(), ll.lat()) } pub fn __repr__(&self) -> String { diff --git a/utiles-pyo3/src/pyutiles/pylnglatbbox.rs b/utiles-pyo3/src/pyutiles/pylnglatbbox.rs index 1469a7e8..4aa68a2c 100644 --- a/utiles-pyo3/src/pyutiles/pylnglatbbox.rs +++ b/utiles-pyo3/src/pyutiles/pylnglatbbox.rs @@ -7,7 +7,7 @@ use pyo3::types::PyType; use pyo3::{exceptions, pyclass, pymethods, Py, PyAny, PyErr, PyRef, PyResult, Python}; use utiles::bbox::BBox; -#[pyclass(name = "LngLatBbox", module = "utiles._utiles")] +#[pyclass(name = "LngLatBbox", module = "utiles._utiles", frozen)] #[derive(Clone)] pub struct PyLngLatBbox { pub bbox: BBox, @@ -22,7 +22,7 @@ impl From for BBox { #[pymethods] impl PyLngLatBbox { #[new] - pub fn new(west: f64, south: f64, east: f64, north: f64) -> Self { + pub fn py_new(west: f64, south: f64, east: f64, north: f64) -> Self { PyLngLatBbox { bbox: BBox { west, @@ -66,7 +66,7 @@ impl PyLngLatBbox { pub fn from_tile(_cls: &Bound<'_, PyType>, tile: &PyTile) -> Self { let ul = utiles::ul(tile.xyz.x, tile.xyz.y, tile.xyz.z); let lr = utiles::lr(tile.xyz.x, tile.xyz.y, tile.xyz.z); - Self::new(ul.lng(), lr.lat(), lr.lng(), ul.lat()) + Self::py_new(ul.lng(), lr.lat(), lr.lng(), ul.lat()) } #[getter] diff --git a/utiles-pyo3/src/pyutiles/pyparsing.rs b/utiles-pyo3/src/pyutiles/pyparsing.rs index 134b2525..e92e181b 100644 --- a/utiles-pyo3/src/pyutiles/pyparsing.rs +++ b/utiles-pyo3/src/pyutiles/pyparsing.rs @@ -14,15 +14,15 @@ pub fn parse_tile_arg(args: &Bound<'_, PyTuple>) -> PyResult { if let Ok(tile) = arg.extract::() { return Ok(tile); } else if let Ok(seq) = arg.extract::<(u32, u32, u8)>() { - return Ok(PyTile::new(seq.0, seq.1, seq.2)); + return Ok(PyTile::py_new(seq.0, seq.1, seq.2)); } else if let Ok(seq) = arg.extract::>() { - return Ok(PyTile::new(seq[0], seq[1], seq[2] as u8)); + return Ok(PyTile::py_new(seq[0], seq[1], seq[2] as u8)); } } else if args.len() == 3 { let x = args.get_item(0)?.extract()?; let y = args.get_item(1)?.extract()?; let z = args.get_item(2)?.extract()?; - return Ok(PyTile::new(x, y, z)); + return Ok(PyTile::py_new(x, y, z)); } Err(PyErr::new::( @@ -38,9 +38,9 @@ pub fn parse_bbox(args: &Bound<'_, PyTuple>) -> PyResult { 1 => { let arg = args.get_item(0)?; if let Ok(bbox) = arg.extract::<(f64, f64, f64, f64)>() { - return Ok(PyLngLatBbox::new(bbox.0, bbox.1, bbox.2, bbox.3)); + return Ok(PyLngLatBbox::py_new(bbox.0, bbox.1, bbox.2, bbox.3)); } else if let Ok(seq) = arg.extract::>() { - return Ok(PyLngLatBbox::new(seq[0], seq[1], seq[2], seq[3])); + return Ok(PyLngLatBbox::py_new(seq[0], seq[1], seq[2], seq[3])); } // raise ValueError("the bbox argument may have 1 or 4 values") Err(PyErr::new::( @@ -50,14 +50,14 @@ pub fn parse_bbox(args: &Bound<'_, PyTuple>) -> PyResult { 2 => { let x = args.get_item(0)?.extract()?; let y = args.get_item(1)?.extract()?; - Ok(PyLngLatBbox::new(x, y, x, y)) + Ok(PyLngLatBbox::py_new(x, y, x, y)) } 4 => { let x1 = args.get_item(0)?.extract()?; let y1 = args.get_item(1)?.extract()?; let x2 = args.get_item(2)?.extract()?; let y2 = args.get_item(3)?.extract()?; - Ok(PyLngLatBbox::new(x1, y1, x2, y2)) + Ok(PyLngLatBbox::py_new(x1, y1, x2, y2)) } _ => Err(PyErr::new::( "the bbox argument may have 1, 2 or 4 values", @@ -80,7 +80,7 @@ pub fn parse_tiles(args: &Bound<'_, PyTuple>) -> PyResult> { if let Ok(x) = args.get_item(0)?.extract::() { let y = args.get_item(1)?.extract()?; let z = args.get_item(2)?.extract()?; - return Ok(vec![PyTile::new(x, y, z)]); + return Ok(vec![PyTile::py_new(x, y, z)]); } } diff --git a/utiles-pyo3/src/pyutiles/pytile.rs b/utiles-pyo3/src/pyutiles/pytile.rs index e376a67f..cadf7368 100644 --- a/utiles-pyo3/src/pyutiles/pytile.rs +++ b/utiles-pyo3/src/pyutiles/pytile.rs @@ -7,7 +7,8 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::{PyDict, PyNotImplemented, PyTuple, PyType}; use pyo3::{ - exceptions, intern, pyclass, pymethods, Py, PyAny, PyErr, PyRef, PyResult, Python, + exceptions, intern, pyclass, pymethods, IntoPyObjectExt, Py, PyAny, PyErr, PyRef, + PyResult, Python, }; use serde::Serialize; use utiles::bbox::BBox; @@ -33,27 +34,25 @@ macro_rules! pytile { }; } -#[pyclass(name = "Tile", module = "utiles._utiles", sequence)] +#[pyclass(name = "Tile", module = "utiles._utiles", sequence, frozen)] #[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash, Copy)] pub struct PyTile { pub xyz: Tile, } -// #[derive(FromPyObject)] -// pub enum PyTileOrTuple { -// Tile(PyTile), -// Tuple(TileTuple), -// } - #[pymethods] impl PyTile { #[new] - pub fn new(x: u32, y: u32, z: u8) -> Self { + pub fn py_new(x: u32, y: u32, z: u8) -> Self { Self { xyz: Tile::new(x, y, z), } } + fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult> { + PyTuple::new(py, [self.xyz.x(), self.xyz.y(), self.xyz.z() as u32]) + } + pub fn valid(&self) -> bool { self.xyz.valid() } @@ -64,8 +63,7 @@ impl PyTile { } pub fn json_arr(&self) -> String { - let s = format!("[{}, {}, {}]", self.xyz.x, self.xyz.y, self.xyz.z); - s + format!("[{}, {}, {}]", self.xyz.x, self.xyz.y, self.xyz.z) } #[pyo3(signature = (obj = true))] @@ -224,10 +222,7 @@ impl PyTile { &self, idx: tuple_slice::SliceOrInt, py: Python<'py>, - ) -> PyResult< - // tuple_slice::TupleSliceResult - Bound<'py, PyAny>, - > { + ) -> PyResult> { match idx { tuple_slice::SliceOrInt::Slice(slice) => { let psi = slice.indices(3)?; @@ -237,27 +232,18 @@ impl PyTile { .step_by(step as usize) .copied() .collect(); - let tuple = - PyTuple::new(py, m) - .map(pyo3::Bound::into_any) - .map_err(|e| { - PyErr::new::(format!("Error: {e}")) - })?; + let tuple = PyTuple::new(py, m).map(Bound::into_any).map_err(|e| { + PyErr::new::(format!("Error: {e}")) + })?; Ok(tuple) } tuple_slice::SliceOrInt::Int(idx) => match idx { - 0 | -3 => { - let r = self.xyz.x.into_pyobject(py).map(pyo3::Bound::into_any)?; - Ok(r) - } - 1 | -2 => { - let r = self.xyz.y.into_pyobject(py).map(pyo3::Bound::into_any)?; - Ok(r) - } + 0 | -3 => self.xyz.x().into_bound_py_any(py), + 1 | -2 => self.xyz.y().into_bound_py_any(py), 2 | -1 => { let r = u32::from(self.xyz.z) .into_pyobject(py) - .map(pyo3::Bound::into_any)?; + .map(Bound::into_any)?; Ok(r) } 3 => Err(PyErr::new::("")), diff --git a/utiles-pyo3/src/pyutiles/pytile_fmts.rs b/utiles-pyo3/src/pyutiles/pytile_fmts.rs index 196cc52e..d484666d 100644 --- a/utiles-pyo3/src/pyutiles/pytile_fmts.rs +++ b/utiles-pyo3/src/pyutiles/pytile_fmts.rs @@ -9,7 +9,7 @@ use utiles::TileStringFormatter; use crate::pyutiles::pyparsing::parse_tile_arg; #[derive(Clone, Debug, PartialEq, Hash)] -#[pyclass(name = "TileFmts", module = "utiles._utiles")] +#[pyclass(name = "TileFmts", module = "utiles._utiles", frozen)] pub struct PyTileFmts { pub tformatter: TileStringFormatter, } @@ -17,7 +17,7 @@ pub struct PyTileFmts { #[pymethods] impl PyTileFmts { #[new] - pub fn new(fmtstr: &str) -> Self { + pub fn py_new(fmtstr: &str) -> Self { PyTileFmts { tformatter: TileStringFormatter::new(fmtstr), } diff --git a/utiles-pyo3/src/pyutiles/pytile_type.rs b/utiles-pyo3/src/pyutiles/pytile_type.rs index 7f48fc57..e63374da 100644 --- a/utiles-pyo3/src/pyutiles/pytile_type.rs +++ b/utiles-pyo3/src/pyutiles/pytile_type.rs @@ -37,7 +37,7 @@ impl FromPyObject<'_> for PyTileEncoding { } } -pub struct PyTileFormat(tile_type::TileFormat); +pub struct PyTileFormat(TileFormat); const TILE_FORMAT_STRINGS: &str = "png, webp, pbf, mvt, gif, jpg, jpeg, json, geojson"; impl FromPyObject<'_> for PyTileFormat { @@ -107,14 +107,7 @@ impl PyTileType { #[getter] fn compression<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> { - match self.0.encoding { - TileEncoding::Uncompressed => intern!(py, "uncompressed"), - TileEncoding::Internal => intern!(py, "internal"), - TileEncoding::Zlib => intern!(py, "zlib"), - TileEncoding::Gzip => intern!(py, "gzip"), - TileEncoding::Brotli => intern!(py, "brotli"), - TileEncoding::Zstd => intern!(py, "zstd"), - } + self.encoding(py) } #[getter] diff --git a/utiles-pyo3/tests/test_tile.py b/utiles-pyo3/tests/test_tile.py index 4dac36d1..26a7a8a0 100644 --- a/utiles-pyo3/tests/test_tile.py +++ b/utiles-pyo3/tests/test_tile.py @@ -1,5 +1,7 @@ from __future__ import annotations +import pickle + import utiles as ut @@ -27,6 +29,12 @@ def test_tile_children() -> None: assert children_2 == children_1 +def test_pickling() -> None: + pickled = pickle.dumps(ut.Tile(0, 0, 0)) + loaded = pickle.loads(pickled) + assert loaded == ut.Tile(0, 0, 0) + + def test_tile_children_zorder() -> None: children = ut.Tile(0, 0, 0).children(zorder=True) children_1 = [ From f13b36ed7dcc129ea71c7ad239afa8dceaf690b3 Mon Sep 17 00:00:00 2001 From: jessekrubin Date: Mon, 10 Mar 2025 13:35:23 -0700 Subject: [PATCH 2/2] pickling the xyz tiles --- utiles-pyo3/src/pyutiles/pytile.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utiles-pyo3/src/pyutiles/pytile.rs b/utiles-pyo3/src/pyutiles/pytile.rs index cadf7368..2d11f608 100644 --- a/utiles-pyo3/src/pyutiles/pytile.rs +++ b/utiles-pyo3/src/pyutiles/pytile.rs @@ -50,7 +50,7 @@ impl PyTile { } fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult> { - PyTuple::new(py, [self.xyz.x(), self.xyz.y(), self.xyz.z() as u32]) + PyTuple::new(py, [self.x(), self.y(), u32::from(self.z())]) } pub fn valid(&self) -> bool {