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 44edeea
Show file tree
Hide file tree
Showing 11 changed files with 1,452 additions and 80 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ jobs:
- run: nox -s clippy-all
env:
CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }}
CARGO_TOOLCHAIN_VERSION: ${{ steps.toolchain.outputs.name }}

build-pr:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }}
Expand Down Expand Up @@ -439,7 +440,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.
53 changes: 27 additions & 26 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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 +55,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 +388,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 +761,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 Down Expand Up @@ -795,30 +795,31 @@ def _get_rust_default_target() -> str:


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

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 cargo_version.split('.') >= (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 44edeea

Please sign in to comment.