Skip to content

Commit

Permalink
Add jiff into/from python convertions
Browse files Browse the repository at this point in the history
  • Loading branch information
bschoenmaeckers committed Jan 17, 2025
1 parent ad5f6d4 commit 2a3d91b
Show file tree
Hide file tree
Showing 11 changed files with 1,464 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ jobs:
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: cargo rustdoc --lib --no-default-features --features full -Zunstable-options --config "build.rustdocflags=[\"--cfg\", \"docsrs\"]"
- run: cargo rustdoc --lib --no-default-features --features full,jiff-01 -Zunstable-options --config "build.rustdocflags=[\"--cfg\", \"docsrs\"]"

coverage:
if: ${{ github.event_name != 'merge_group' }}
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ either = { version = "1.9", optional = true }
eyre = { version = ">= 0.6.8, < 0.7", optional = true }
hashbrown = { version = ">= 0.14.5, < 0.16", optional = true }
indexmap = { version = ">= 2.5.0, < 3", optional = true }
jiff-01 = { package = "jiff", version = "0.1.18", optional = true }
num-bigint = { version = "0.4.2", optional = true }
num-complex = { version = ">= 0.4.6, < 0.5", optional = true }
num-rational = {version = "0.4.1", optional = true }
Expand Down
13 changes: 13 additions & 0 deletions guide/src/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ Adds a dependency on [hashbrown](https://docs.rs/hashbrown) and enables conversi

Adds a dependency on [indexmap](https://docs.rs/indexmap) and enables conversions into its [`IndexMap`](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html) type.

### `jiff-01`

Adds a dependency on [[email protected]](https://docs.rs/jiff/0.1) and requires MSRV 1.70. Enables a conversion from [jiff](https://docs.rs/jiff)'s types to python:
- [Span](https://docs.rs/jiff/0.1/jiff/struct.Span.html) -> [`PyDelta`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDelta.html)
- [SignedDuration](https://docs.rs/jiff/0.1/jiff/struct.SignedDuration.html) -> [`PyDelta`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDelta.html)
- [TimeZone](https://docs.rs/jiff/0.1/jiff/tz/struct.TimeZone.html) -> [`PyTzInfo`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTzInfo.html)
- [Offset](https://docs.rs/jiff/0.1/jiff/tz/struct.Offset.html) -> [`PyTzInfo`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTzInfo.html)
- [Date](https://docs.rs/jiff/0.1/jiff/civil/struct.Date.html) -> [`PyDate`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDate.html)
- [Time](https://docs.rs/jiff/0.1/jiff/civil/struct.Time.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html)
- [DateTime](https://docs.rs/jiff/0.1/jiff/civil/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
- [Zoned](https://docs.rs/jiff/0.1/jiff/struct.Zoned.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
- [Timestamp](https://docs.rs/jiff/0.1/jiff/struct.Timestamp.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)

### `num-bigint`

Adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conversions into its [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types.
Expand Down
1 change: 1 addition & 0 deletions newsfragments/4823.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add jiff to/from python conversions.
64 changes: 37 additions & 27 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@
from functools import lru_cache
from glob import glob
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
Optional,
Tuple,
Generator,
)

import nox
import nox.command
Expand Down Expand Up @@ -55,9 +65,9 @@ def test_rust(session: nox.Session):
if not FREE_THREADED_BUILD:
_run_cargo_test(session, features="abi3")
if "skip-full" not in session.posargs:
_run_cargo_test(session, features="full")
_run_cargo_test(session, features="full jiff-01")
if not FREE_THREADED_BUILD:
_run_cargo_test(session, features="abi3 full")
_run_cargo_test(session, features="abi3 full jiff-01")


@nox.session(name="test-py", venv_backend="none")
Expand Down Expand Up @@ -388,7 +398,7 @@ def docs(session: nox.Session) -> None:
"doc",
"--lib",
"--no-default-features",
"--features=full",
"--features=full,jiff-01",
"--no-deps",
"--workspace",
*cargo_flags,
Expand Down Expand Up @@ -761,8 +771,8 @@ def update_ui_tests(session: nox.Session):
env["TRYBUILD"] = "overwrite"
command = ["test", "--test", "test_compile_error"]
_run_cargo(session, *command, env=env)
_run_cargo(session, *command, "--features=full", env=env)
_run_cargo(session, *command, "--features=abi3,full", env=env)
_run_cargo(session, *command, "--features=full,jiff-01", env=env)
_run_cargo(session, *command, "--features=abi3,full,jiff-01", env=env)


def _build_docs_for_ffi_check(session: nox.Session) -> None:
Expand All @@ -779,7 +789,7 @@ def _get_rust_info() -> Tuple[str, ...]:
return tuple(output.splitlines())


def _get_rust_version() -> Tuple[int, int, int, List[str]]:
def get_rust_version() -> Tuple[int, int, int, List[str]]:
for line in _get_rust_info():
if line.startswith(_RELEASE_LINE_START):
version = line[len(_RELEASE_LINE_START) :].strip()
Expand All @@ -795,30 +805,30 @@ def _get_rust_default_target() -> str:


@lru_cache()
def _get_feature_sets() -> Tuple[Tuple[str, ...], ...]:
def _get_feature_sets() -> Generator[Tuple[str, ...], None, None]:
"""Returns feature sets to use for clippy job"""
cargo_target = os.getenv("CARGO_BUILD_TARGET", "")

yield from (
("--no-default-features",),
(
"--no-default-features",
"--features=abi3",
),
)

features = "full"

if "wasm32-wasip1" not in cargo_target:
# multiple-pymethods not supported on wasm
return (
("--no-default-features",),
(
"--no-default-features",
"--features=abi3",
),
("--features=full multiple-pymethods",),
("--features=abi3 full multiple-pymethods",),
)
else:
return (
("--no-default-features",),
(
"--no-default-features",
"--features=abi3",
),
("--features=full",),
("--features=abi3 full",),
)
features += ",multiple-pymethods"

if get_rust_version()[:2] >= (1, 70):
# jiff needs MSRC 1.70+
features += ",jiff-01"

yield (f"--features={features}",)
yield (f"--features=abi3,{features}",)


_RELEASE_LINE_START = "release: "
Expand Down
54 changes: 3 additions & 51 deletions src/conversions/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@
use crate::conversion::IntoPyObject;
use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError};
#[cfg(Py_LIMITED_API)]
use crate::sync::GILOnceCell;
use crate::intern;
use crate::types::any::PyAnyMethods;
#[cfg(not(Py_LIMITED_API))]
use crate::types::datetime::timezone_from_offset;
#[cfg(Py_LIMITED_API)]
use crate::types::datetime_abi3::{check_type, timezone_utc, DatetimeTypes};
#[cfg(Py_LIMITED_API)]
use crate::types::IntoPyDict;
use crate::types::PyNone;
#[cfg(not(Py_LIMITED_API))]
Expand All @@ -57,8 +59,6 @@ use crate::types::{
PyTzInfo, PyTzInfoAccess,
};
use crate::{ffi, Bound, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python};
#[cfg(Py_LIMITED_API)]
use crate::{intern, DowncastError};
#[allow(deprecated)]
use crate::{IntoPy, ToPyObject};
use chrono::offset::{FixedOffset, Utc};
Expand Down Expand Up @@ -811,54 +811,6 @@ fn py_time_to_naive_time(py_time: &Bound<'_, PyAny>) -> PyResult<NaiveTime> {
.ok_or_else(|| PyValueError::new_err("invalid or out-of-range time"))
}

#[cfg(Py_LIMITED_API)]
fn check_type(value: &Bound<'_, PyAny>, t: &PyObject, type_name: &'static str) -> PyResult<()> {
if !value.is_instance(t.bind(value.py()))? {
return Err(DowncastError::new(value, type_name).into());
}
Ok(())
}

#[cfg(Py_LIMITED_API)]
struct DatetimeTypes {
date: PyObject,
datetime: PyObject,
time: PyObject,
timedelta: PyObject,
timezone: PyObject,
timezone_utc: PyObject,
tzinfo: PyObject,
}

#[cfg(Py_LIMITED_API)]
impl DatetimeTypes {
fn get(py: Python<'_>) -> &Self {
Self::try_get(py).expect("failed to load datetime module")
}

fn try_get(py: Python<'_>) -> PyResult<&Self> {
static TYPES: GILOnceCell<DatetimeTypes> = GILOnceCell::new();
TYPES.get_or_try_init(py, || {
let datetime = py.import("datetime")?;
let timezone = datetime.getattr("timezone")?;
Ok::<_, PyErr>(Self {
date: datetime.getattr("date")?.into(),
datetime: datetime.getattr("datetime")?.into(),
time: datetime.getattr("time")?.into(),
timedelta: datetime.getattr("timedelta")?.into(),
timezone_utc: timezone.getattr("utc")?.into(),
timezone: timezone.into(),
tzinfo: datetime.getattr("tzinfo")?.into(),
})
})
}
}

#[cfg(Py_LIMITED_API)]
fn timezone_utc(py: Python<'_>) -> Bound<'_, PyAny> {
DatetimeTypes::get(py).timezone_utc.bind(py).clone()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit 2a3d91b

Please sign in to comment.