Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
896 changes: 896 additions & 0 deletions crates/sqltide-python/src/shapes/adapter_models.rs

Large diffs are not rendered by default.

76 changes: 73 additions & 3 deletions crates/sqltide-python/src/shapes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use pyo3::types::{
IntoPyDict, PyCFunction, PyDict, PyFloat, PyFrozenSet, PyString, PyTuple, PyType,
};

mod adapter_models;
mod cli_service;
mod compiler_analysis;
mod compiler_project;
Expand Down Expand Up @@ -186,7 +187,10 @@ macro_rules! shapes {
kwargs: Option<&pyo3::Bound<'_, pyo3::types::PyDict>>,
) -> pyo3::PyResult<pyo3::PyClassInitializer<Self>> {
let values: Vec<pyo3::Bound<'_, pyo3::PyAny>> =
$crate::shapes::bind(&Self::SPEC, args, kwargs)?;
match $crate::shapes::copied_values(args, kwargs)? {
Some(values) => values,
None => $crate::shapes::bind(&Self::SPEC, args, kwargs)?,
};
Ok($crate::shapes::shapes!(@init values $($base)?))
}

Expand Down Expand Up @@ -249,8 +253,27 @@ macro_rules! shapes {
$crate::shapes::missing_attribute(slf.as_any(), name)
}

/// Rebuilds the shape from its field values, so `copy`, `deepcopy` and `pickle`
/// work as they did on the dataclass.
/// A new instance of the same class sharing every field value, as `copy.copy` of
/// the dataclass gave.
fn __copy__<'py>(
slf: &pyo3::Bound<'py, Self>,
) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {
$crate::shapes::shallow_copy(slf.as_any(), slf.get().values(slf.py()))
}

/// `copy.replace` on Python 3.13 and later, through `dataclasses.replace` as the
/// dataclass's own `__replace__` did.
#[pyo3(signature = (**changes))]
fn __replace__<'py>(
slf: &pyo3::Bound<'py, Self>,
changes: Option<&pyo3::Bound<'py, pyo3::types::PyDict>>,
) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {
use pyo3::types::PyAnyMethods;
slf.py().import("dataclasses")?.getattr("replace")?.call((slf,), changes)
}

/// Rebuilds the shape from its field values, so `deepcopy` and `pickle` work as they
/// did on the dataclass.
fn __reduce__<'py>(
slf: &pyo3::Bound<'py, Self>,
) -> pyo3::PyResult<(
Expand Down Expand Up @@ -283,6 +306,52 @@ pub(crate) fn next<'py>(
.ok_or_else(|| PyValueError::new_err("shape constructor bound too few values"))
}

/// The private first constructor argument with which `copy.copy` hands a shape class the field
/// values to keep as they are, so a copy of any subclass is built without rebinding them.
static COPY_MARKER: PyOnceLock<Py<PyAny>> = PyOnceLock::new();

fn copy_marker(py: Python<'_>) -> PyResult<&Py<PyAny>> {
COPY_MARKER.get_or_try_init(py, || {
Ok(py.import("builtins")?.getattr("object")?.call0()?.unbind())
})
}

/// The field values `shallow_copy` passed to the constructor, or `None` for any other call.
pub(crate) fn copied_values<'py>(
args: &Bound<'py, PyTuple>,
kwargs: Option<&Bound<'py, PyDict>>,
) -> PyResult<Option<Vec<Bound<'py, PyAny>>>> {
if args.len() != 2 || kwargs.is_some_and(|kwargs| !kwargs.is_empty()) {
return Ok(None);
}
if !args.get_item(0)?.is(copy_marker(args.py())?) {
return Ok(None);
}
let values: Bound<'py, PyTuple> = args.get_item(1)?.cast_into()?;
Ok(Some(values.iter().collect()))
}

/// `copy.copy` as it worked on the dataclass: a new instance of the instance's own class that
/// shares every field value, without rerunning normalization, post-init checks or a subclass
/// `__init__`, and with a copy of a Python subclass instance's `__dict__`.
pub(crate) fn shallow_copy<'py>(
instance: &Bound<'py, PyAny>,
values: Vec<Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
let py: Python<'py> = instance.py();
let class: Bound<'py, PyType> = instance.get_type();
let copied: Bound<'py, PyAny> =
class
.getattr("__new__")?
.call1((&class, copy_marker(py)?, PyTuple::new(py, values)?))?;
if class.getattr("__dictoffset__")?.extract::<isize>()? != 0 {
copied
.getattr("__dict__")?
.call_method1("update", (instance.getattr("__dict__")?,))?;
}
Ok(copied)
}

/// Native enums by their `sqltide._native` attribute, for defaults and coercions.
static ENUMS: PyOnceLock<Py<PyDict>> = PyOnceLock::new();

Expand Down Expand Up @@ -877,6 +946,7 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
cli_service::register(module)?;
sensors_dev::register(module)?;
discovery_models::register(module)?;
adapter_models::register(module)?;
lazy_constants(
module,
&[
Expand Down
2 changes: 1 addition & 1 deletion python-boundary.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# still carries planning, validation, rendering or other movable logic counts as movable until
# that logic moves to Rust.
# Native failures must surface as errors; `handled_native_errors` lists the reviewed exceptions.
remaining_lines = 1080
remaining_lines = 0

[[stays]]
path = "adapter/classes/adapter.py"
Expand Down
Loading
Loading