Skip to content

Commit

Permalink
add python 3.13 wheels, now that its ABI is stable
Browse files Browse the repository at this point in the history
  • Loading branch information
ariebovenberg committed Aug 6, 2024
1 parent 27ecc62 commit 758ad52
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 61 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: "1.79"
components: "clippy, rustfmt"
- run: |
pip install .
pip install -U pip
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
if : ${{ matrix.os == 'windows' }}
with:
python-version: '3.12'
python-version: '3.13'
architecture: ${{ matrix.python-architecture || 'x64' }}
allow-prereleases: true
- run: pip install -U twine
- name: Generate third-party license information
run: |
Expand All @@ -73,7 +75,7 @@ jobs:
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --strip --out dist --interpreter '3.9 3.10 3.11 3.12'
args: --release --strip --out dist --interpreter '3.9 3.10 3.11 3.12 3.13'
manylinux: ${{ matrix.manylinux || 'auto' }}
sccache: 'true'
rust-toolchain: "1.79"
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
🚀 Changelog
============

0.6.7 (2024-08-06)
------------------

- Add Python 3.13 binary wheels, now that its ABI is stable
- Small improvements to import speed

0.6.6 (2024-07-27)
------------------

Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![](https://img.shields.io/github/actions/workflow/status/ariebovenberg/whenever/checks.yml?branch=main&style=flat-square)](https://github.com/ariebovenberg/whenever)
[![](https://img.shields.io/readthedocs/whenever.svg?style=flat-square)](http://whenever.readthedocs.io/)

**Typed and DST-safe datetimes for Python, written in Rust\***
**Typed and DST-safe datetimes for Python, available in speedy Rust or pure Python.**

Do you cross your fingers every time you work with Python's datetime—hoping that you didn't mix naive and aware?
or that you avoided its [other pitfalls](https://dev.arie.bovenberg.net/blog/python-datetime-pitfalls/)?
Expand All @@ -20,6 +20,8 @@ There’s no way to be sure...
*Whenever* helps you write **correct** and **type checked** datetime code.
Mistakes become <span style="text-decoration: underline; text-decoration-color: red; text-decoration-style: wavy">red squiggles</span> in your IDE, instead of bugs in production.
It's also **way faster** than other third-party libraries—and usually the standard library as well.
If performance isn't your top priority, a **pure Python** version is available as well.


<p align="center">
<picture align="center">
Expand Down Expand Up @@ -50,8 +52,6 @@ It's also **way faster** than other third-party libraries—and usually the stan
> as we gather feedback and improve the library.
> Leave a ⭐️ on github if you'd like to see how this project develops!
\**Skeptical of Rust? Don't worry, it's available in pure Python too!*

## Why not the standard library?

Over 20+ years, Python's `datetime` has grown
Expand Down Expand Up @@ -218,6 +218,5 @@ This project is inspired by the following projects. Check them out!

- [Noda Time](https://nodatime.org/) and [Joda Time](https://www.joda.org/joda-time/)
- [Temporal](https://tc39.es/proposal-temporal/docs/)
- [Chrono](https://docs.rs/chrono/latest/chrono/)

The benchmark comparison graph is based on the one from the [Ruff](https://github.com/astral-sh/ruff) project.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ authors = [
{name = "Arie Bovenberg", email = "a.c.bovenberg@gmail.com"},
]
readme = "README.md"
version = "0.6.6"
version = "0.6.7"
description = "Modern datetime library for Python, written in Rust"
requires-python = ">=3.9"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion pysrc/whenever/_pywhenever.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# - It saves some overhead
from __future__ import annotations

__version__ = "0.6.6"
__version__ = "0.6.7"

import enum
import re
Expand Down
100 changes: 47 additions & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,7 @@ unsafe fn create_enum(name: &CStr, members: &[(&CStr, i32)]) -> PyReturn {

unsafe fn new_exc(module: *mut PyObject, name: &CStr, base: *mut PyObject) -> *mut PyObject {
let e = PyErr_NewException(name.as_ptr(), base, NULL());
if e.is_null() {
return NULL();
}
defer_decref!(e);
if PyModule_AddType(module, e.cast()) != 0 {
if e.is_null() || PyModule_AddType(module, e.cast()) != 0 {
return NULL();
}
e
Expand Down Expand Up @@ -239,9 +235,8 @@ unsafe fn new_type<T: PyWrapped>(
let Some(pyvalue) = value.to_obj(cls).ok() else {
return false;
};
// NOTE: we don't decref the value here on purpose.
// Singletons work out refcount/GC-wise in the end.
if PyDict_SetItemString((*cls).tp_dict, name.as_ptr().cast(), pyvalue) != 0 {
defer_decref!(pyvalue);
if PyDict_SetItemString((*cls).tp_dict, name.as_ptr(), pyvalue) != 0 {
return false;
}
}
Expand Down Expand Up @@ -440,46 +435,45 @@ unsafe extern "C" fn module_exec(module: *mut PyObject) -> c_int {

// Making time patcheable results in a performance hit.
// Only enable it if the time_machine module is available.
state.time_machine_exists = match PyImport_ImportModule(c"time_machine".as_ptr()).as_result() {
Ok(m) => {
Py_DecRef(m);
true
}
Err(_) => {
PyErr_Clear();
false
}
};

state.time_machine_exists = unwrap_or_errcode!(time_machine_installed());
state.time_patch = TimePatch::Unset;

0
}

unsafe fn do_visit(target: *mut PyObject, visit: visitproc, arg: *mut c_void) {
let obj: *mut PyObject = target.cast();
if !obj.is_null() {
(visit)(obj, arg);
unsafe fn time_machine_installed() -> PyResult<bool> {
// Important: we don't import the `time_machine` here,
// because that would be slower. We only need to check its existence.
let find_spec = PyObject_GetAttrString(
steal!(PyImport_ImportModule(c"importlib.util".as_ptr()).as_result()?),
c"find_spec".as_ptr(),
);
defer_decref!(find_spec);
let spec = call1(find_spec, steal!("time_machine".to_py()?))?;
defer_decref!(spec);
Ok((spec as *mut PyObject) != Py_None())
}

unsafe fn traverse(target: *mut PyObject, visit: visitproc, arg: *mut c_void) {
if !target.is_null() {
(visit)(target, arg);
}
}

unsafe fn do_type_visit(
unsafe fn traverse_type(
target: *mut PyTypeObject,
visit: visitproc,
arg: *mut c_void,
num_singletons: usize,
) {
let obj: *mut PyObject = target.cast();
if !obj.is_null() {
(visit)(obj, arg);
// XXX: This trick SEEMS to let us avoid adding GC
// support to our types: Since our types are atomic and immutable
// this should be allowed...
if !target.is_null() {
// XXX: This trick SEEMS to let us avoid adding GC support to our types.
// Since our types are atomic and immutable this should be allowed...
// ...BUT there is a reference cycle between the class and the
// singleton instances (e.g. the Date.MAX instance and Date class itself)
// Visiting the type once for each singleton should make GC aware of this.
for _ in 0..num_singletons {
(visit)(obj, arg);
for _ in 0..(num_singletons + 1) {
(visit)(target.cast(), arg);
}
}
}
Expand All @@ -491,46 +485,46 @@ unsafe extern "C" fn module_traverse(
) -> c_int {
let state = State::for_mod(module);
// types
do_type_visit(state.date_type, visit, arg, date::SINGLETONS.len());
do_type_visit(state.time_type, visit, arg, time::SINGLETONS.len());
do_type_visit(
traverse_type(state.date_type, visit, arg, date::SINGLETONS.len());
traverse_type(state.time_type, visit, arg, time::SINGLETONS.len());
traverse_type(
state.date_delta_type,
visit,
arg,
date_delta::SINGLETONS.len(),
);
do_type_visit(
traverse_type(
state.time_delta_type,
visit,
arg,
time_delta::SINGLETONS.len(),
);
do_type_visit(
traverse_type(
state.datetime_delta_type,
visit,
arg,
datetime_delta::SINGLETONS.len(),
);
do_type_visit(
traverse_type(
state.local_datetime_type,
visit,
arg,
local_datetime::SINGLETONS.len(),
);
do_type_visit(state.instant_type, visit, arg, instant::SINGLETONS.len());
do_type_visit(
traverse_type(state.instant_type, visit, arg, instant::SINGLETONS.len());
traverse_type(
state.offset_datetime_type,
visit,
arg,
offset_datetime::SINGLETONS.len(),
);
do_type_visit(
traverse_type(
state.zoned_datetime_type,
visit,
arg,
zoned_datetime::SINGLETONS.len(),
);
do_type_visit(
traverse_type(
state.system_datetime_type,
visit,
arg,
Expand All @@ -539,22 +533,22 @@ unsafe extern "C" fn module_traverse(

// enum members
for &member in state.weekday_enum_members.iter() {
do_visit(member, visit, arg);
traverse(member, visit, arg);
}

// exceptions
do_visit(state.exc_repeated, visit, arg);
do_visit(state.exc_skipped, visit, arg);
do_visit(state.exc_invalid_offset, visit, arg);
do_visit(state.exc_implicitly_ignoring_dst, visit, arg);
traverse(state.exc_repeated, visit, arg);
traverse(state.exc_skipped, visit, arg);
traverse(state.exc_invalid_offset, visit, arg);
traverse(state.exc_implicitly_ignoring_dst, visit, arg);

// Imported modules
do_visit(state.zoneinfo_type, visit, arg);
do_visit(state.timezone_type, visit, arg);
do_visit(state.strptime, visit, arg);
do_visit(state.format_rfc2822, visit, arg);
do_visit(state.parse_rfc2822, visit, arg);
do_visit(state.time_ns, visit, arg);
traverse(state.zoneinfo_type, visit, arg);
traverse(state.timezone_type, visit, arg);
traverse(state.strptime, visit, arg);
traverse(state.format_rfc2822, visit, arg);
traverse(state.parse_rfc2822, visit, arg);
traverse(state.time_ns, visit, arg);

0
}
Expand Down

0 comments on commit 758ad52

Please sign in to comment.