Skip to content

Commit

Permalink
few more fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jessekrubin committed Mar 10, 2025
1 parent 839b721 commit 720d1f1
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 52 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/ryo3-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ categories.workspace = true
[dependencies]
http.workspace = true
pyo3 = { workspace = true, features = ["experimental-inspect"] }
ryo3-core.workspace = true
ryo3-macros.workspace = true

[build-dependencies]
Expand Down
67 changes: 29 additions & 38 deletions crates/ryo3-http/src/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use crate::PyHeadersLike;
use http::header::HeaderMap;
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyString, PyTuple};
use std::collections::HashMap;
use pyo3::types::{PyBytes, PyDict, PyList, PyString, PyTuple};

#[pyclass(name = "Headers", module = "ry")]
#[derive(Clone, Debug)]
Expand All @@ -21,25 +20,13 @@ impl From<HeaderMap> for PyHeaders {
impl PyHeaders {
#[new]
#[pyo3(signature = (d = None))]
fn py_new(_py: Python<'_>, d: Option<HashMap<String, String>>) -> PyResult<Self> {
let mut headers = HeaderMap::new();
fn py_new(_py: Python<'_>, d: Option<PyHeadersLike>) -> PyResult<Self> {
if let Some(d) = d {
for (k, v) in d {
let header_name =
http::header::HeaderName::from_bytes(k.as_bytes()).map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"header-name-error: {e} (k={k}, v={v})"
))
})?;
let header_value = http::header::HeaderValue::from_str(&v).map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"header-value-error: {e} (k={k}, v={v})"
))
})?;
headers.insert(header_name, header_value);
}
let headers_map = HeaderMap::try_from(d)?;
Ok(Self(headers_map))
} else {
Ok(PyHeaders(HeaderMap::new()))
}
Ok(Self(headers))
}

fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
Expand Down Expand Up @@ -286,28 +273,32 @@ impl PyHeaders {
fn asdict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
let d = PyDict::new(py);

for (k, v) in &self.0 {
let k = k.as_str();
if let Ok(vstr) = v.to_str() {
d.set_item(k, vstr)?;
for key in self.0.keys() {
let key_str = key.as_str();
let values: Vec<_> = self.0.get_all(key).iter().collect();

if values.len() == 1 {
let v = values[0];
if let Ok(vstr) = v.to_str() {
d.set_item(key_str, vstr)?;
} else {
let pybytes = PyBytes::new(py, v.as_bytes());
d.set_item(key_str, pybytes)?;
}
} else {
let pybytes = PyBytes::new(py, v.as_bytes());
d.set_item(k, pybytes)?;
let py_list = PyList::empty(py);
for v in values {
if let Ok(vstr) = v.to_str() {
py_list.append(vstr)?;
} else {
let pybytes = PyBytes::new(py, v.as_bytes());
py_list.append(pybytes)?;
}
}
d.set_item(key_str, py_list)?;
}
}

Ok(d)
}

// pub fn __ior__<'py>(
// mut slf: PyRefMut<'py, Self>,
// other: &PyHeaders,
// ) -> PyResult<PyRefMut<'py, Self>> {
// let other_headers = other.0.clone();
// for (k, v) in other_headers {
// if let Some(k) = k {
// slf.0.insert(k, v);
// }
// }
// Ok(slf)
// }
}
61 changes: 48 additions & 13 deletions crates/ryo3-http/src/headers_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,38 @@ use crate::PyHeaders;
use http::HeaderMap;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use ryo3_core::types::StringOrStrings;
use std::collections::HashMap;

#[derive(Debug, Clone, FromPyObject)]
pub enum PyHeadersLike {
Headers(PyHeaders),
Map(HashMap<String, String>),
Map(HashMap<String, StringOrStrings>),
}

impl PyHeadersLike {
pub fn map2headers(d: &HashMap<String, String>) -> PyResult<HeaderMap> {
pub fn map2headers(d: &HashMap<String, StringOrStrings>) -> PyResult<HeaderMap> {
let mut headers = HeaderMap::new();
for (k, v) in d {
let header_name = http::header::HeaderName::from_bytes(k.as_bytes())
.map_err(|e| PyValueError::new_err(format!("header-name-error: {e}")))?;
let header_value = http::header::HeaderValue::from_str(v)
.map_err(|e| PyValueError::new_err(format!("header-value-error: {e}")))?;
headers.insert(header_name, header_value);
match v {
StringOrStrings::String(s) => {
let header_name = http::header::HeaderName::from_bytes(k.as_bytes())
.map_err(|e| PyValueError::new_err(format!("header-name-error: {e}")))?;
let header_value = http::header::HeaderValue::from_str(s)
.map_err(|e| PyValueError::new_err(format!("header-value-error: {e}")))?;
headers.insert(header_name, header_value);
}
StringOrStrings::Strings(v) => {
let header_name = http::header::HeaderName::from_bytes(k.as_bytes())
.map_err(|e| PyValueError::new_err(format!("header-name-error: {e}")))?;
for s in v {
let header_value = http::header::HeaderValue::from_str(s).map_err(|e| {
PyValueError::new_err(format!("header-value-error: {e}"))
})?;
headers.append(&header_name, header_value);
}
}
}
}
Ok(headers)
}
Expand All @@ -33,12 +48,32 @@ impl TryFrom<PyHeadersLike> for HeaderMap {
let mut default_headers = HeaderMap::new();
for (k, v) in d {
let k = k.to_string();
let v = v.to_string();
let header_name = http::header::HeaderName::from_bytes(k.as_bytes())
.map_err(|e| PyValueError::new_err(format!("header-name-error: {e}")))?;
let header_value = http::header::HeaderValue::from_str(&v)
.map_err(|e| PyValueError::new_err(format!("header-value-error: {e}")))?;
default_headers.insert(header_name, header_value);
match v {
StringOrStrings::String(s) => {
let header_name = http::header::HeaderName::from_bytes(k.as_bytes())
.map_err(|e| {
PyValueError::new_err(format!("header-name-error: {e}"))
})?;
let header_value =
http::header::HeaderValue::from_str(&s).map_err(|e| {
PyValueError::new_err(format!("header-value-error: {e}"))
})?;
default_headers.insert(header_name, header_value);
}
StringOrStrings::Strings(v) => {
let header_name = http::header::HeaderName::from_bytes(k.as_bytes())
.map_err(|e| {
PyValueError::new_err(format!("header-name-error: {e}"))
})?;
for s in v {
let header_value = http::header::HeaderValue::from_str(&s)
.map_err(|e| {
PyValueError::new_err(format!("header-value-error: {e}"))
})?;
default_headers.append(&header_name, header_value);
}
}
}
}
Ok(default_headers)
}
Expand Down
23 changes: 23 additions & 0 deletions crates/ryo3-size/src/size_formatter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::types::{Base, Style};
use pyo3::prelude::*;
use pyo3::types::PyTuple;
use pyo3::IntoPyObjectExt;

#[pyclass(name = "SizeFormatter", module = "ry")]
pub struct PySizeFormatter {
Expand Down Expand Up @@ -27,6 +29,27 @@ impl PySizeFormatter {
}
}

fn __eq__(&self, other: &PySizeFormatter) -> bool {
self.base == other.base && self.style == other.style
}

fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
let base = match self.base.0 {
size::fmt::Base::Base2 => Some(2),
size::fmt::Base::Base10 => Some(10),
_ => None,
};
let style = match self.style.0 {
size::fmt::Style::Abbreviated => Some("abbreviated"),
size::fmt::Style::Full => Some("full"),
_ => None,
};
PyTuple::new(
py,
&[base.into_bound_py_any(py)?, style.into_bound_py_any(py)?],
)
}

fn format(&self, n: i64) -> String {
self.formatter.format(n)
}
Expand Down
30 changes: 30 additions & 0 deletions crates/ryo3-size/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@ use pyo3::prelude::*;
use pyo3::types::{PyInt, PyString};
use std::fmt::Display;

#[derive(Debug)]
pub struct Base(pub size::fmt::Base);

impl PartialEq for Base {
fn eq(&self, other: &Self) -> bool {
matches!(
(self.0, other.0),
(size::fmt::Base::Base2, size::fmt::Base::Base2)
| (size::fmt::Base::Base10, size::fmt::Base::Base10)
)
}
}

impl Default for Base {
fn default() -> Self {
Base(size::fmt::Base::Base10)
Expand Down Expand Up @@ -44,6 +55,25 @@ impl FromPyObject<'_> for Base {
#[derive(Debug)]
pub struct Style(pub size::fmt::Style);

impl PartialEq for Style {
fn eq(&self, other: &Self) -> bool {
matches!(
(self.0, other.0),
(size::fmt::Style::Default, size::fmt::Style::Default)
| (size::fmt::Style::Abbreviated, size::fmt::Style::Abbreviated)
| (
size::fmt::Style::AbbreviatedLowercase,
size::fmt::Style::AbbreviatedLowercase
)
| (size::fmt::Style::Full, size::fmt::Style::Full)
| (
size::fmt::Style::FullLowercase,
size::fmt::Style::FullLowercase
)
)
}
}

impl Default for Style {
fn default() -> Self {
Style(size::fmt::Style::Default)
Expand Down
7 changes: 6 additions & 1 deletion python/ry/http.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

import typing as t

class Headers:
"""python-ryo3-http `http::HeadersMap` wrapper"""

def __init__(self, headers: dict[str, str]) -> None: ...
def __init__(self, headers: dict[str, str | t.Sequence[str]]) -> None: ...

# =========================================================================
# STRING
Expand All @@ -19,6 +23,7 @@ class Headers:
def __delitem__(self, key: str) -> None: ...
def __contains__(self, key: str) -> bool: ...
def __or__(self, other: Headers | dict[str, str]) -> Headers: ...
def asdict(self) -> dict[str, str | t.Sequence[str]]: ...

# =========================================================================
# INSTANCE METHODS
Expand Down
7 changes: 7 additions & 0 deletions tests/_http/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ def test_headers_multiple_same_key_get(self) -> None:
assert content_type == "application/json"
assert isinstance(content_type, str)

# dump to dict and see if its the same...
d = h.asdict()
expected_dict = {"content-type": ["application/json", "application/xml"]}
assert d == expected_dict
from_dict = Headers(d)
assert from_dict == h

def test_headers_multiple_same_key_get_all(self) -> None:
h = Headers({"Content-Type": "application/json"})
h.append("content-Type", "application/xml")
Expand Down

0 comments on commit 720d1f1

Please sign in to comment.