Skip to content

Commit

Permalink
wip support for json cow
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Mar 20, 2024
1 parent 36a1b6c commit acfed80
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 64 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -44,7 +44,7 @@ base64 = "0.21.7"
num-bigint = "0.4.4"
python3-dll-a = "0.2.7"
uuid = "1.7.0"
jiter = {version = "0.0.7", features = ["python"]}
jiter = { git = "https://github.com/pydantic/jiter", branch = "string-cow", features = ["python"] }

[lib]
name = "_pydantic_core"
Expand Down
2 changes: 1 addition & 1 deletion src/errors/line_error.rs
Expand Up @@ -151,7 +151,7 @@ impl ValLineError {
#[derive(Clone)]
pub enum InputValue {
Python(PyObject),
Json(JsonValue),
Json(JsonValue<'static>),
}

impl ToPyObject for InputValue {
Expand Down
7 changes: 7 additions & 0 deletions src/errors/location.rs
@@ -1,5 +1,6 @@
use pyo3::exceptions::PyTypeError;
use pyo3::sync::GILOnceCell;
use std::borrow::Cow;
use std::fmt;

use pyo3::prelude::*;
Expand Down Expand Up @@ -52,6 +53,12 @@ impl From<&str> for LocItem {
}
}

impl From<Cow<'_, str>> for LocItem {
fn from(s: Cow<'_, str>) -> Self {
Self::S(s.into_owned())
}
}

impl From<i64> for LocItem {
fn from(i: i64) -> Self {
Self::I(i)
Expand Down
72 changes: 36 additions & 36 deletions src/input/input_json.rs
Expand Up @@ -24,26 +24,26 @@ use super::{
};

/// This is required but since JSON object keys are always strings, I don't think it can be called
impl From<&JsonValue> for LocItem {
impl From<&JsonValue<'_>> for LocItem {
fn from(json_value: &JsonValue) -> Self {
match json_value {
JsonValue::Int(i) => (*i).into(),
JsonValue::Str(s) => s.as_str().into(),
JsonValue::Str(s) => s.clone().into(),
v => format!("{v:?}").into(),
}
}
}

impl From<JsonValue> for LocItem {
impl From<JsonValue<'_>> for LocItem {
fn from(json_value: JsonValue) -> Self {
(&json_value).into()
}
}

impl<'py> Input<'py> for JsonValue {
impl<'py, 'data> Input<'py> for JsonValue<'data> {
fn as_error_value(&self) -> InputValue {
// cloning JsonValue is cheap due to use of Arc
InputValue::Json(self.clone())
InputValue::Json(self.clone().into_static())
}

fn is_none(&self) -> bool {
Expand All @@ -63,19 +63,19 @@ impl<'py> Input<'py> for JsonValue {
}
}

type Arguments<'a> = JsonArgs<'a>
type Arguments<'a> = JsonArgs<'a, 'data>
where
Self: 'a,;

fn validate_args(&self) -> ValResult<JsonArgs<'_>> {
fn validate_args(&self) -> ValResult<JsonArgs<'_, 'data>> {
match self {
JsonValue::Object(object) => Ok(JsonArgs::new(None, Some(object))),
JsonValue::Array(array) => Ok(JsonArgs::new(Some(array), None)),
_ => Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self)),
}
}

fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<JsonArgs<'a>> {
fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<JsonArgs<'a, 'data>> {
match self {
JsonValue::Object(object) => Ok(JsonArgs::new(None, Some(object))),
_ => {
Expand All @@ -98,7 +98,7 @@ impl<'py> Input<'py> for JsonValue {
// TODO: in V3 we may want to make JSON str always win if in union, for consistency,
// see https://github.com/pydantic/pydantic-core/pull/867#discussion_r1386582501
match self {
JsonValue::Str(s) => Ok(ValidationMatch::strict(s.as_str().into())),
JsonValue::Str(s) => Ok(ValidationMatch::strict(s.as_ref().into())),
JsonValue::Int(i) if !strict && coerce_numbers_to_str => Ok(ValidationMatch::lax(i.to_string().into())),
JsonValue::BigInt(b) if !strict && coerce_numbers_to_str => Ok(ValidationMatch::lax(b.to_string().into())),
JsonValue::Float(f) if !strict && coerce_numbers_to_str => Ok(ValidationMatch::lax(f.to_string().into())),
Expand Down Expand Up @@ -142,7 +142,7 @@ impl<'py> Input<'py> for JsonValue {

fn exact_str(&self) -> ValResult<EitherString<'_>> {
match self {
JsonValue::Str(s) => Ok(s.as_str().into()),
JsonValue::Str(s) => Ok(s.as_ref().into()),
_ => Err(ValError::new(ErrorTypeDefaults::StringType, self)),
}
}
Expand All @@ -168,7 +168,7 @@ impl<'py> Input<'py> for JsonValue {
}
}

type Dict<'a> = &'a JsonObject;
type Dict<'a> = &'a JsonObject<'data> where Self: 'a;

fn validate_dict(&self, _strict: bool) -> ValResult<Self::Dict<'_>> {
match self {
Expand All @@ -181,9 +181,9 @@ impl<'py> Input<'py> for JsonValue {
self.validate_dict(false)
}

type List<'a> = &'a JsonArray;
type List<'a> = &'a JsonArray<'data> where Self: 'a;

fn validate_list(&self, _strict: bool) -> ValMatch<&JsonArray> {
fn validate_list(&self, _strict: bool) -> ValMatch<&JsonArray<'data>> {
match self {
JsonValue::Array(a) => Ok(ValidationMatch::strict(a)),
_ => Err(ValError::new(ErrorTypeDefaults::ListType, self)),
Expand Down Expand Up @@ -320,7 +320,7 @@ impl<'py> Input<'py> for str {
fn as_error_value(&self) -> InputValue {
// Justification for the clone: this is on the error pathway and we are generally ok
// with errors having a performance penalty
InputValue::Json(JsonValue::Str(self.to_owned()))
InputValue::Json(JsonValue::Str(self.to_owned().into()))
}

fn as_kwargs(&self, _py: Python<'py>) -> Option<Bound<'py, PyDict>> {
Expand Down Expand Up @@ -458,21 +458,21 @@ impl BorrowInput<'_> for String {
}
}

impl BorrowInput<'_> for JsonValue {
type Input = JsonValue;
impl<'data> BorrowInput<'_> for JsonValue<'data> {
type Input = JsonValue<'data>;
fn borrow_input(&self) -> &Self::Input {
self
}
}

fn string_to_vec(s: &str) -> JsonArray {
JsonArray::new(s.chars().map(|c| JsonValue::Str(c.to_string())).collect())
JsonArray::new(s.chars().map(|c| JsonValue::Str(c.to_string().into())).collect())
}

impl<'py> ValidatedDict<'py> for &'_ JsonObject {
impl<'py, 'data> ValidatedDict<'py> for &'_ JsonObject<'data> {
type Key<'a> = &'a str where Self: 'a;

type Item<'a> = &'a JsonValue where Self: 'a;
type Item<'a> = &'a JsonValue<'data> where Self: 'a;

fn get_item<'k>(&self, key: &'k LookupKey) -> ValResult<Option<(&'k LookupPath, Self::Item<'_>)>> {
key.json_get(self)
Expand All @@ -486,12 +486,12 @@ impl<'py> ValidatedDict<'py> for &'_ JsonObject {
&'a self,
consumer: impl ConsumeIterator<ValResult<(Self::Key<'a>, Self::Item<'a>)>, Output = R>,
) -> ValResult<R> {
Ok(consumer.consume_iterator(LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_str(), v)))))
Ok(consumer.consume_iterator(LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_ref(), v)))))
}
}

impl<'a, 'py> ValidatedList<'py> for &'a JsonArray {
type Item = &'a JsonValue;
impl<'a, 'data, 'py> ValidatedList<'py> for &'a JsonArray<'data> {
type Item = &'a JsonValue<'data>;

fn len(&self) -> Option<usize> {
Some(SmallVec::len(self))
Expand All @@ -505,20 +505,20 @@ impl<'a, 'py> ValidatedList<'py> for &'a JsonArray {
}

#[cfg_attr(debug_assertions, derive(Debug))]
pub struct JsonArgs<'a> {
args: Option<&'a [JsonValue]>,
kwargs: Option<&'a JsonObject>,
pub struct JsonArgs<'a, 'data> {
args: Option<&'a [JsonValue<'data>]>,
kwargs: Option<&'a JsonObject<'data>>,
}

impl<'a> JsonArgs<'a> {
fn new(args: Option<&'a [JsonValue]>, kwargs: Option<&'a JsonObject>) -> Self {
impl<'a, 'data> JsonArgs<'a, 'data> {
fn new(args: Option<&'a [JsonValue<'data>]>, kwargs: Option<&'a JsonObject<'data>>) -> Self {
Self { args, kwargs }
}
}

impl<'a> Arguments<'_> for JsonArgs<'a> {
type Args = [JsonValue];
type Kwargs = JsonObject;
impl<'a, 'data> Arguments<'_> for JsonArgs<'a, 'data> {
type Args = [JsonValue<'data>];
type Kwargs = JsonObject<'data>;

fn args(&self) -> Option<&Self::Args> {
self.args
Expand All @@ -529,8 +529,8 @@ impl<'a> Arguments<'_> for JsonArgs<'a> {
}
}

impl PositionalArgs<'_> for [JsonValue] {
type Item<'a> = &'a JsonValue;
impl<'data> PositionalArgs<'_> for [JsonValue<'data>] {
type Item<'a> = &'a JsonValue<'data> where Self: 'a;

fn len(&self) -> usize {
<[JsonValue]>::len(self)
Expand All @@ -543,9 +543,9 @@ impl PositionalArgs<'_> for [JsonValue] {
}
}

impl KeywordArgs<'_> for JsonObject {
type Key<'a> = &'a str;
type Item<'a> = &'a JsonValue;
impl<'data> KeywordArgs<'_> for JsonObject<'data> {
type Key<'a> = &'a str where Self: 'a;
type Item<'a> = &'a JsonValue<'data> where Self: 'a;

fn len(&self) -> usize {
LazyIndexMap::len(self)
Expand All @@ -554,6 +554,6 @@ impl KeywordArgs<'_> for JsonObject {
key.json_get(self)
}
fn iter(&self) -> impl Iterator<Item = ValResult<(Self::Key<'_>, Self::Item<'_>)>> {
LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_str(), v)))
LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_ref(), v)))
}
}
42 changes: 29 additions & 13 deletions src/input/return_enums.rs
Expand Up @@ -81,8 +81,8 @@ pub enum GenericIterable<'a, 'py> {
PyByteArray(&'a Bound<'py, PyByteArray>),
Sequence(&'a Bound<'py, PySequence>),
Iterator(Bound<'py, PyIterator>),
JsonArray(&'a [JsonValue]),
JsonObject(&'a JsonObject),
JsonArray(&'a [JsonValue<'a>]),
JsonObject(&'a JsonObject<'a>),
JsonString(&'a str),
}

Expand Down Expand Up @@ -565,20 +565,29 @@ pub(crate) fn iterate_attributes<'a, 'py>(
})
}

#[derive(Debug, Clone)]
pub enum GenericIterator {
#[derive(Debug)]
pub enum GenericIterator<'a> {
PyIterator(GenericPyIterator),
JsonArray(GenericJsonIterator),
JsonArray(GenericJsonIterator<'a>),
}

impl From<JsonArray> for GenericIterator {
fn from(array: JsonArray) -> Self {
impl GenericIterator<'_> {
pub(crate) fn into_static(self) -> GenericIterator<'static> {
match self {
GenericIterator::PyIterator(iter) => GenericIterator::PyIterator(iter),
GenericIterator::JsonArray(iter) => GenericIterator::JsonArray(iter.into_static()),
}
}
}

impl<'a> From<JsonArray<'a>> for GenericIterator<'a> {
fn from(array: JsonArray<'a>) -> Self {
let json_iter = GenericJsonIterator { array, index: 0 };
Self::JsonArray(json_iter)
}
}

impl From<&Bound<'_, PyAny>> for GenericIterator {
impl From<&Bound<'_, PyAny>> for GenericIterator<'_> {
fn from(obj: &Bound<'_, PyAny>) -> Self {
let py_iter = GenericPyIterator {
obj: obj.clone().into(),
Expand Down Expand Up @@ -621,13 +630,13 @@ impl GenericPyIterator {
}

#[derive(Debug, Clone)]
pub struct GenericJsonIterator {
array: JsonArray,
pub struct GenericJsonIterator<'a> {
array: JsonArray<'a>,
index: usize,
}

impl GenericJsonIterator {
pub fn next(&mut self, _py: Python) -> PyResult<Option<(&JsonValue, usize)>> {
impl<'a> GenericJsonIterator<'a> {
pub fn next(&mut self, _py: Python) -> PyResult<Option<(&JsonValue<'a>, usize)>> {
if self.index < self.array.len() {
// panic here is impossible due to bounds check above; compiler should be
// able to optimize it away even
Expand All @@ -641,12 +650,19 @@ impl GenericJsonIterator {
}

pub fn input_as_error_value(&self, _py: Python<'_>) -> InputValue {
InputValue::Json(JsonValue::Array(self.array.clone()))
InputValue::Json(JsonValue::Array(self.array.clone()).into_static())
}

pub fn index(&self) -> usize {
self.index
}

pub fn into_static(self) -> GenericJsonIterator<'static> {
GenericJsonIterator {
array: JsonArray::new(self.array.iter().map(|v| v.clone().into_static()).collect()),
index: self.index,
}
}
}

#[cfg_attr(debug_assertions, derive(Debug))]
Expand Down
18 changes: 9 additions & 9 deletions src/lookup_key.rs
Expand Up @@ -260,12 +260,12 @@ impl LookupKey {
}
}

pub fn json_get<'data, 's>(
pub fn json_get<'a, 'data, 's>(
&'s self,
dict: &'data JsonObject,
) -> ValResult<Option<(&'s LookupPath, &'data JsonValue)>> {
dict: &'a JsonObject<'data>,
) -> ValResult<Option<(&'s LookupPath, &'a JsonValue<'data>)>> {
match self {
Self::Simple { key, path, .. } => match dict.get(key) {
Self::Simple { key, path, .. } => match dict.get(key.as_str()) {
Some(value) => Ok(Some((path, value))),
None => Ok(None),
},
Expand All @@ -275,9 +275,9 @@ impl LookupKey {
key2,
path2,
..
} => match dict.get(key1) {
} => match dict.get(key1.as_str()) {
Some(value) => Ok(Some((path1, value))),
None => match dict.get(key2) {
None => match dict.get(key2.as_str()) {
Some(value) => Ok(Some((path2, value))),
None => Ok(None),
},
Expand Down Expand Up @@ -475,7 +475,7 @@ impl PathItem {
}
}

pub fn json_get<'a>(&self, any_json: &'a JsonValue) -> Option<&'a JsonValue> {
pub fn json_get<'a, 'data>(&self, any_json: &'a JsonValue<'data>) -> Option<&'a JsonValue<'data>> {
match any_json {
JsonValue::Object(v_obj) => self.json_obj_get(v_obj),
JsonValue::Array(v_array) => match self {
Expand All @@ -493,9 +493,9 @@ impl PathItem {
}
}

pub fn json_obj_get<'a>(&self, json_obj: &'a JsonObject) -> Option<&'a JsonValue> {
pub fn json_obj_get<'a, 'data>(&self, json_obj: &'a JsonObject<'data>) -> Option<&'a JsonValue<'data>> {
match self {
Self::S(key, _) => json_obj.get(key),
Self::S(key, _) => json_obj.get(key.as_str()),
_ => None,
}
}
Expand Down

0 comments on commit acfed80

Please sign in to comment.