Skip to content

Commit

Permalink
Support python timedelta objects in duration (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
kylebarron authored Jan 15, 2025
1 parent 36f876a commit 38a1254
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 7 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.

2 changes: 1 addition & 1 deletion obstore/python/obstore/_get.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class BytesStream:
To fix this, set the `timeout` parameter in the `client_options` passed to the
initial `get` or `get_async` call. See
[ClientConfigKey][obstore.store.ClientConfigKey].
[ClientConfig][obstore.store.ClientConfig].
"""

def __aiter__(self) -> BytesStream:
Expand Down
35 changes: 30 additions & 5 deletions obstore/python/obstore/store/_client.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
from datetime import timedelta
from typing import TypedDict

class ClientConfig(TypedDict, total=False):
"""HTTP client configuration"""
"""HTTP client configuration
For timeout values (`connect_timeout`, `http2_keep_alive_timeout`,
`pool_idle_timeout`, and `timeout`), values can either be Python `timedelta`
objects, or they can be "human-readable duration strings".
The human-readable duration string is a concatenation of time spans. Where each time
span is an integer number and a suffix. Supported suffixes:
- `nsec`, `ns` -- nanoseconds
- `usec`, `us` -- microseconds
- `msec`, `ms` -- milliseconds
- `seconds`, `second`, `sec`, `s`
- `minutes`, `minute`, `min`, `m`
- `hours`, `hour`, `hr`, `h`
- `days`, `day`, `d`
- `weeks`, `week`, `w`
- `months`, `month`, `M` -- defined as 30.44 days
- `years`, `year`, `y` -- defined as 365.25 days
For example:
- `"2h 37min"`
- `"32ms"`
"""

allow_http: bool
"""Allow non-TLS, i.e. non-HTTPS connections."""
Expand All @@ -16,21 +41,21 @@ class ClientConfig(TypedDict, total=False):
introduces significant vulnerabilities, and should only be used
as a last resort or for testing
"""
connect_timeout: str
connect_timeout: str | timedelta
"""Timeout for only the connect phase of a Client"""
default_content_type: str
"""default `CONTENT_TYPE` for uploads"""
http1_only: bool
"""Only use http1 connections."""
http2_keep_alive_interval: str
"""Interval for HTTP2 Ping frames should be sent to keep a connection alive."""
http2_keep_alive_timeout: str
http2_keep_alive_timeout: str | timedelta
"""Timeout for receiving an acknowledgement of the keep-alive ping."""
http2_keep_alive_while_idle: str
"""Enable HTTP2 keep alive pings for idle connections"""
http2_only: bool
"""Only use http2 connections"""
pool_idle_timeout: str
pool_idle_timeout: str | timedelta
"""The pool max idle timeout.
This is the length of time an idle connection will be kept alive.
Expand All @@ -39,7 +64,7 @@ class ClientConfig(TypedDict, total=False):
"""Maximum number of idle connections per host."""
proxy_url: str
"""HTTP proxy to use for requests."""
timeout: str
timeout: str | timedelta
"""Request timeout.
The timeout is applied from when the request starts connecting until the
Expand Down
2 changes: 2 additions & 0 deletions pyo3-object_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ include = ["src", "type-hints", "README.md", "LICENSE"]

[dependencies]
futures = "0.3"
# This is already an object_store dependency
humantime = "2.1"
object_store = { version = "0.11.2", features = [
"aws",
"azure",
Expand Down
8 changes: 7 additions & 1 deletion pyo3-object_store/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
use std::time::Duration;

use humantime::format_duration;
use pyo3::prelude::*;

/// A wrapper around `String` used to store values for config values.
///
/// Supported Python input:
///
/// - str
/// - `True` and `False` (becomes `"true"` and `"false"`)
/// - `timedelta`
/// - `str`
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct PyConfigValue(pub String);

impl<'py> FromPyObject<'py> for PyConfigValue {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
if let Ok(val) = ob.extract::<bool>() {
Ok(Self(val.to_string()))
} else if let Ok(duration) = ob.extract::<Duration>() {
Ok(Self(format_duration(duration).to_string()))
} else {
Ok(Self(ob.extract()?))
}
Expand Down
9 changes: 9 additions & 0 deletions tests/store/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from datetime import timedelta

from obstore.store import HTTPStore


def test_config_timedelta():
HTTPStore.from_url(
"https://example.com", client_options={"timeout": timedelta(seconds=30)}
)

0 comments on commit 38a1254

Please sign in to comment.