Skip to content

Commit

Permalink
associated types for arguments in Input
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Mar 19, 2024
1 parent 0587e15 commit a745068
Show file tree
Hide file tree
Showing 8 changed files with 578 additions and 418 deletions.
96 changes: 85 additions & 11 deletions src/input/input_abstract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ use pyo3::exceptions::PyValueError;
use pyo3::types::{PyDict, PyList, PyType};
use pyo3::{intern, prelude::*};

use crate::errors::{ErrorTypeDefaults, InputValue, ValError, ValResult};
use crate::errors::{ErrorTypeDefaults, InputValue, LocItem, ValError, ValResult};
use crate::lookup_key::{LookupKey, LookupPath};
use crate::tools::py_err;
use crate::{PyMultiHostUrl, PyUrl};

use super::datetime::{EitherDate, EitherDateTime, EitherTime, EitherTimedelta};
use super::return_enums::{EitherBytes, EitherInt, EitherString};
use super::{EitherFloat, GenericArguments, GenericIterable, GenericIterator, GenericMapping, ValidationMatch};
use super::{EitherFloat, GenericIterable, GenericIterator, GenericMapping, ValidationMatch};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputType {
Expand Down Expand Up @@ -85,9 +86,13 @@ pub trait Input<'py>: fmt::Debug + ToPyObject {
false
}

fn validate_args(&self) -> ValResult<GenericArguments<'_, 'py>>;
type Arguments<'a>: Arguments<'py>
where
Self: 'a;

fn validate_dataclass_args<'a>(&'a self, dataclass_name: &str) -> ValResult<GenericArguments<'a, 'py>>;
fn validate_args(&self) -> ValResult<Self::Arguments<'_>>;

fn validate_dataclass_args<'a>(&'a self, dataclass_name: &str) -> ValResult<Self::Arguments<'a>>;

fn validate_str(&self, strict: bool, coerce_numbers_to_str: bool) -> ValMatch<EitherString<'_>>;

Expand Down Expand Up @@ -234,14 +239,40 @@ impl<'py, T: Input<'py> + ?Sized> BorrowInput<'py> for &'_ T {
}
}

pub enum Never {}

// Pairs with Iterable below
pub trait ConsumeIterator<T> {
type Output;
fn consume_iterator(self, iterator: impl Iterator<Item = T>) -> Self::Output;
}

pub trait Arguments<'py> {
type Args: PositionalArgs<'py> + ?Sized;
type Kwargs: KeywordArgs<'py> + ?Sized;

fn args(&self) -> Option<&Self::Args>;
fn kwargs(&self) -> Option<&Self::Kwargs>;
}

pub trait PositionalArgs<'py> {
type Item<'a>: BorrowInput<'py>
where
Self: 'a;
fn len(&self) -> usize;
fn get_item(&self, index: usize) -> Option<Self::Item<'_>>;
fn iter(&self) -> impl Iterator<Item = Self::Item<'_>>;
}
pub trait KeywordArgs<'py> {
type Key<'a>: BorrowInput<'py> + Clone + Into<LocItem>
where
Self: 'a;
type Item<'a>: BorrowInput<'py> + ToPyObject
where
Self: 'a;
fn len(&self) -> usize;
fn get_item<'k>(&self, key: &'k LookupKey) -> ValResult<Option<(&'k LookupPath, Self::Item<'_>)>>;
fn iter(&self) -> impl Iterator<Item = ValResult<(Self::Key<'_>, Self::Item<'_>)>>;
}

// This slightly awkward trait is used to define types which can be iterable. This formulation
// arises because the Python enums have several different underlying iterator types, and we want to
// be able to dispatch over each of them without overhead.
Expand All @@ -251,6 +282,16 @@ pub trait Iterable<'py> {
fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Input>, Output = R>) -> ValResult<R>;
}

// Optimization pathway for inputs which are already python lists
pub trait AsPyList<'py>: Iterable<'py> {
fn as_py_list(&self) -> Option<&Bound<'py, PyList>>;
}

/// This type is used for inputs which don't support certain types.
/// It implements all the associated traits, but never actually gets called.
pub enum Never {}

// Necessary for inputs which don't support certain types, e.g. String -> list
impl<'py> Iterable<'py> for Never {
type Input = Bound<'py, PyAny>; // Doesn't really matter what this is
Expand All @@ -262,13 +303,46 @@ impl<'py> Iterable<'py> for Never {
}
}

// Optimization pathway for inputs which are already python lists
pub trait AsPyList<'py>: Iterable<'py> {
fn as_py_list(&self) -> Option<&Bound<'py, PyList>>;
}

impl<'py> AsPyList<'py> for Never {
fn as_py_list(&self) -> Option<&Bound<'py, PyList>> {
unreachable!()
}
}

impl Arguments<'_> for Never {
type Args = Never;
type Kwargs = Never;
fn args(&self) -> Option<&Self::Args> {
unreachable!()
}
fn kwargs(&self) -> Option<&Self::Kwargs> {
unreachable!()
}
}

impl<'py> PositionalArgs<'py> for Never {
type Item<'a> = Bound<'py, PyAny> where Self: 'a;
fn len(&self) -> usize {
unreachable!()
}
fn get_item(&self, _index: usize) -> Option<Self::Item<'_>> {
unreachable!()
}
fn iter(&self) -> impl Iterator<Item = Self::Item<'_>> {
[].into_iter()
}
}

impl<'py> KeywordArgs<'py> for Never {
type Key<'a> = Bound<'py, PyAny> where Self: 'a;
type Item<'a> = Bound<'py, PyAny> where Self: 'a;
fn len(&self) -> usize {
unreachable!()
}
fn get_item<'k>(&self, _key: &'k LookupKey) -> ValResult<Option<(&'k LookupPath, Self::Item<'_>)>> {
unreachable!()
}
fn iter(&self) -> impl Iterator<Item = ValResult<(Self::Key<'_>, Self::Item<'_>)>> {
[].into_iter()
}
}
90 changes: 79 additions & 11 deletions src/input/input_json.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::borrow::Cow;

use jiter::{JsonArray, JsonValue};
use jiter::{JsonArray, JsonObject, JsonValue, LazyIndexMap};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString};
use smallvec::SmallVec;
use speedate::MicrosecondsPrecisionOverflowBehavior;
use strum::EnumMessage;

use crate::errors::{ErrorType, ErrorTypeDefaults, InputValue, LocItem, ValError, ValResult};
use crate::lookup_key::{LookupKey, LookupPath};
use crate::validators::decimal::create_decimal;

use super::datetime::{
Expand All @@ -18,8 +19,8 @@ use super::input_abstract::{AsPyList, ConsumeIterator, Iterable, Never, ValMatch
use super::return_enums::ValidationMatch;
use super::shared::{float_as_int, int_as_bool, str_as_bool, str_as_float, str_as_int};
use super::{
BorrowInput, EitherBytes, EitherFloat, EitherInt, EitherString, EitherTimedelta, GenericArguments, GenericIterable,
GenericIterator, GenericMapping, Input, JsonArgs,
Arguments, BorrowInput, EitherBytes, EitherFloat, EitherInt, EitherString, EitherTimedelta, GenericIterable,
GenericIterator, GenericMapping, Input, KeywordArgs, PositionalArgs,
};

/// This is required but since JSON object keys are always strings, I don't think it can be called
Expand Down Expand Up @@ -53,7 +54,7 @@ impl<'py> Input<'py> for JsonValue {
match self {
JsonValue::Object(object) => {
let dict = PyDict::new_bound(py);
for (k, v) in object.iter() {
for (k, v) in LazyIndexMap::iter(object) {
dict.set_item(k, v.to_object(py)).unwrap();
}
Some(dict)
Expand All @@ -62,17 +63,21 @@ impl<'py> Input<'py> for JsonValue {
}
}

fn validate_args(&self) -> ValResult<GenericArguments<'_, 'py>> {
type Arguments<'a> = JsonArgs<'a>
where
Self: 'a,;

fn validate_args(&self) -> ValResult<JsonArgs<'_>> {
match self {
JsonValue::Object(object) => Ok(JsonArgs::new(None, Some(object)).into()),
JsonValue::Array(array) => Ok(JsonArgs::new(Some(array), None).into()),
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<GenericArguments<'a, 'py>> {
fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<JsonArgs<'a>> {
match self {
JsonValue::Object(object) => Ok(JsonArgs::new(None, Some(object)).into()),
JsonValue::Object(object) => Ok(JsonArgs::new(None, Some(object))),
_ => {
let class_name = class_name.to_string();
Err(ValError::new(
Expand Down Expand Up @@ -320,13 +325,15 @@ impl<'py> Input<'py> for str {
None
}

type Arguments<'a> = Never;

#[cfg_attr(has_coverage_attribute, coverage(off))]
fn validate_args(&self) -> ValResult<GenericArguments<'_, 'py>> {
fn validate_args(&self) -> ValResult<Never> {
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
}

#[cfg_attr(has_coverage_attribute, coverage(off))]
fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<GenericArguments<'a, 'py>> {
fn validate_dataclass_args(&self, class_name: &str) -> ValResult<Never> {
let class_name = class_name.to_string();
Err(ValError::new(
ErrorType::DataclassType {
Expand Down Expand Up @@ -447,6 +454,13 @@ impl BorrowInput<'_> for String {
}
}

impl BorrowInput<'_> for JsonValue {
type Input = JsonValue;
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())
}
Expand All @@ -467,3 +481,57 @@ impl<'py> AsPyList<'py> for &'_ JsonArray {
None
}
}

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

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

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

fn args(&self) -> Option<&Self::Args> {
self.args
}

fn kwargs(&self) -> Option<&Self::Kwargs> {
self.kwargs
}
}

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

fn len(&self) -> usize {
<[JsonValue]>::len(self)
}
fn get_item(&self, index: usize) -> Option<Self::Item<'_>> {
self.get(index)
}
fn iter(&self) -> impl Iterator<Item = Self::Item<'_>> {
<[JsonValue]>::iter(self)
}
}

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

fn len(&self) -> usize {
LazyIndexMap::len(self)
}
fn get_item<'k>(&self, key: &'k LookupKey) -> ValResult<Option<(&'k LookupPath, Self::Item<'_>)>> {
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)))
}
}
Loading

0 comments on commit a745068

Please sign in to comment.