Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add jiff into/from python convertions #4823

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Icxolu marked this conversation as resolved.
Show resolved Hide resolved
- [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
Loading