Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
608 changes: 387 additions & 221 deletions Cargo.lock

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock"]
clap = { version = "4.5", features = ["derive", "wrap_help"] }
indexmap = "2.5"
regex = "1.10"
fancy-regex = "0.13"
fancy-regex = "0.18"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "2.0"
Expand All @@ -65,12 +65,12 @@ walkdir = "2.5"

# RFC 0017 — OS interface, networking, subprocess.
mio = { version = "1.0", features = ["os-poll", "os-ext", "net"] }
socket2 = { version = "0.5", features = ["all"] }
sha2 = "0.10"
sha1 = "0.10"
md-5 = "0.10"
digest = "0.10"
hmac = "0.12"
socket2 = { version = "0.6", features = ["all"] }
sha2 = "0.11"
sha1 = "0.11"
md-5 = "0.11"
digest = "0.11"
hmac = "0.13"
base64 = "0.22"
crc32fast = "1.4"
flate2 = { version = "1.0", default-features = false, features = ["rust_backend"] }
Expand All @@ -82,17 +82,17 @@ num-traits = "0.2"
num-rational = "0.4"
byteorder = "1.5"
encoding_rs = "0.8"
bzip2 = { version = "0.4", features = ["static"] }
bzip2 = { version = "0.6", features = ["static"] }
xz2 = "0.1"
rusqlite = { version = "0.31", features = ["bundled"] }
rusqlite = { version = "0.40", features = ["bundled"] }
rust_decimal = "1.36"

# RFC 0020 — interactive REPL + CLI surface.
rustyline = { version = "14.0", default-features = false, features = ["with-file-history"] }
dirs = "5.0"
rustyline = { version = "18.0", default-features = false, features = ["with-file-history"] }
dirs = "6.0"

# RFC 0022 — C-API foundation (dlopen + cc-driven extension build harness).
libloading = "0.8"
libloading = "0.9"
cc = "1.0"

# RFC 0023 — drop-in stdlib parity + HTTPS.
Expand All @@ -103,8 +103,8 @@ libc = "0.2"
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
rustls-pki-types = "1.7"
rustls-pemfile = "2.1"
rustls-native-certs = "0.7"
webpki-roots = "0.26"
rustls-native-certs = "0.8"
webpki-roots = "1.0"

# RFC 0024 — real OS threads, GIL, cycle GC, weakrefs.
parking_lot = "0.12"
Expand Down
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ work.
> typing) and is green on `main`. `RFC 0028` adds the PEP 3118 buffer
> protocol, PEP 590 vectorcall, the full `PyType_FromSpec[WithBases]`
> slot surface, and a `_ndarray.c` C-extension fixture exercising the
> stack end-to-end — paving the way for `numpy`, `pandas`, `pillow`,
> and other binary-extension consumers. The CPython `Lib/test/`
> allowlist remains an aspirational target — see
> `tests/regrtest/expectations.toml` for the per-test baseline. Expect
> small breaking changes around the edges as the long tail catches up.
> stack end-to-end. `RFC 0029` closes the loop: the `datetime` C-API,
> the full `PyCapsule` surface, keyword-aware `PyArg_ParseTupleAndKeywords`,
> property-aware descriptor dispatch in `tp_getset`, a numpy-shaped
> `_numpylike.c` fixture exercising `dtype`/ufuncs/buffer-protocol/
> reshape/`mask_select`/`PyDateTime`, a PEP 425 wheel-tag matcher in
> `_minipip` (so binary wheels resolve), and an end-to-end regression
> test that installs a binary wheel under a private prefix and imports
> the bundled extension through the regular `ExtensionFileLoader`
> path — proving the `numpy` install-and-run story works
> mechanically. The CPython `Lib/test/` allowlist remains an
> aspirational target — see `tests/regrtest/expectations.toml` for
> the per-test baseline. Expect small breaking changes around the
> edges as the long tail catches up.

## Repository layout

Expand Down
4 changes: 4 additions & 0 deletions crates/weavepy-capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ thiserror = { workspace = true }

[build-dependencies]
cc = "1.0"

[dev-dependencies]
weavepy = { workspace = true }
tempfile = "3"
11 changes: 11 additions & 0 deletions crates/weavepy-capi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ fn main() {
name: "_ndarray",
env_var: "WEAVEPY_CAPI_NDARRAY_EXTENSION",
});
let numpylike_src = workspace_root.join("tests/capi_ext/_numpylike.c");
build_extension(ExtensionBuild {
cc: &cc,
manifest_dir: &manifest_dir,
out_dir: &out_dir,
target_os: &target_os,
suffix,
src: &numpylike_src,
name: "_numpylike",
env_var: "WEAVEPY_CAPI_NUMPYLIKE_EXTENSION",
});

// Re-export the include directory so dependent crates can see
// `Python.h` via `DEP_WEAVEPY_CAPI_INCLUDE`.
Expand Down
74 changes: 74 additions & 0 deletions crates/weavepy-capi/include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -975,13 +975,27 @@ PyAPI_FUNC(void *) PyCapsule_GetPointer(PyObject *capsule, const char *name);
PyAPI_FUNC(const char *) PyCapsule_GetName(PyObject *capsule);
PyAPI_FUNC(int) PyCapsule_IsValid(PyObject *capsule, const char *name);
PyAPI_FUNC(int) PyCapsule_SetPointer(PyObject *capsule, void *pointer);
PyAPI_FUNC(int) PyCapsule_SetName(PyObject *capsule, const char *name);
PyAPI_FUNC(PyCapsule_Destructor) PyCapsule_GetDestructor(PyObject *capsule);
PyAPI_FUNC(int) PyCapsule_SetDestructor(PyObject *capsule, PyCapsule_Destructor destructor);
PyAPI_FUNC(void *) PyCapsule_GetContext(PyObject *capsule);
PyAPI_FUNC(int) PyCapsule_SetContext(PyObject *capsule, void *context);
PyAPI_FUNC(void *) PyCapsule_Import(const char *name, int no_block);

/* ------------------------------------------------------------------
* Slice helpers.
* ------------------------------------------------------------------ */

PyAPI_FUNC(PyObject *) PySlice_New(PyObject *start, PyObject *stop, PyObject *step);
PyAPI_FUNC(int) PySlice_Check(PyObject *o);
PyAPI_FUNC(int) PySlice_Unpack(PyObject *slice, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step);
PyAPI_FUNC(Py_ssize_t) PySlice_AdjustIndices(Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t step);
PyAPI_FUNC(int) PySlice_GetIndicesEx(PyObject *slice, Py_ssize_t length,
Py_ssize_t *start, Py_ssize_t *stop,
Py_ssize_t *step, Py_ssize_t *slicelength);
PyAPI_FUNC(int) PySlice_GetIndices(PyObject *slice, Py_ssize_t length,
Py_ssize_t *start, Py_ssize_t *stop,
Py_ssize_t *step);

/* ------------------------------------------------------------------
* Hash helpers and atomic refcount operations.
Expand Down Expand Up @@ -1023,6 +1037,66 @@ PyAPI_FUNC(PyObject *) PyDict_GetItemWithError(PyObject *d, PyObject *k);
#define _Py_SIZE_ROUND_DOWN(n, a) ((size_t)(n) & ~(size_t)((a)-1))
#define _Py_ALIGN_UP(p, a) ((void *)_Py_SIZE_ROUND_UP((uintptr_t)(p), (a)))

/* ------------------------------------------------------------------
* Datetime C-API (RFC 0029).
*
* Mirrors CPython's `datetime.h` interface — the in-process capsule
* named `datetime.datetime_CAPI` carries the function-pointer table
* extensions consume to construct date/time/datetime/timedelta
* objects without round-tripping through Python.
*
* The actual `PyDateTime_CAPI` struct layout lives in
* `datetime_api.rs`; we expose the type-check and direct-constructor
* symbols here so simple extensions can use them without grabbing
* the capsule.
* ------------------------------------------------------------------ */
PyAPI_FUNC(PyObject *) PyDate_FromDate(int year, int month, int day);
PyAPI_FUNC(PyObject *) PyDateTime_FromDateAndTime(int year, int month, int day, int hour, int minute, int second, int usec);
PyAPI_FUNC(PyObject *) PyTime_FromTime(int hour, int minute, int second, int usec);
PyAPI_FUNC(PyObject *) PyDelta_FromDSU(int days, int seconds, int microseconds);
PyAPI_FUNC(PyObject *) PyTimeZone_FromOffset(PyObject *offset);
PyAPI_FUNC(PyObject *) PyTimeZone_FromOffsetAndName(PyObject *offset, PyObject *name);

PyAPI_FUNC(int) PyDateTime_GET_YEAR(PyObject *o);
PyAPI_FUNC(int) PyDateTime_GET_MONTH(PyObject *o);
PyAPI_FUNC(int) PyDateTime_GET_DAY(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DATE_GET_HOUR(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DATE_GET_MINUTE(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DATE_GET_SECOND(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DATE_GET_MICROSECOND(PyObject *o);
PyAPI_FUNC(int) PyDateTime_TIME_GET_HOUR(PyObject *o);
PyAPI_FUNC(int) PyDateTime_TIME_GET_MINUTE(PyObject *o);
PyAPI_FUNC(int) PyDateTime_TIME_GET_SECOND(PyObject *o);
PyAPI_FUNC(int) PyDateTime_TIME_GET_MICROSECOND(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DELTA_GET_DAYS(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DELTA_GET_SECONDS(PyObject *o);
PyAPI_FUNC(int) PyDateTime_DELTA_GET_MICROSECONDS(PyObject *o);

PyAPI_FUNC(int) PyDate_Check(PyObject *o);
PyAPI_FUNC(int) PyDate_CheckExact(PyObject *o);
PyAPI_FUNC(int) PyDateTime_Check(PyObject *o);
PyAPI_FUNC(int) PyDateTime_CheckExact(PyObject *o);
PyAPI_FUNC(int) PyTime_Check(PyObject *o);
PyAPI_FUNC(int) PyTime_CheckExact(PyObject *o);
PyAPI_FUNC(int) PyDelta_Check(PyObject *o);
PyAPI_FUNC(int) PyDelta_CheckExact(PyObject *o);
PyAPI_FUNC(int) PyTZInfo_Check(PyObject *o);
PyAPI_FUNC(int) PyTZInfo_CheckExact(PyObject *o);

/* Convenience: pull the datetime C-API capsule. Extensions that
* use the `PyDateTime_IMPORT` macro from CPython's datetime.h
* call this once at module init. Returns NULL on failure
* with an ImportError pending. */
#define PyDateTime_IMPORT \
(PyDateTimeAPI = (PyDateTime_CAPI *)PyCapsule_Import("datetime.datetime_CAPI", 0))

/* The shape of the capsule payload — opaque to user code; the
* full definition lives on the Rust side. We declare it as an
* incomplete type and only expose it through the macros above. */
typedef struct PyDateTime_CAPI PyDateTime_CAPI;
PyAPI_DATA(PyDateTime_CAPI) PyDateTimeAPI_Instance;
extern PyDateTime_CAPI *PyDateTimeAPI;

#ifdef __cplusplus
}
#endif
Expand Down
Loading
Loading