用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ariebovenberg/whenever --skill rust-ffi命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | rust-ffi |
| description | Instructions for using whenever's internal Rust FFI abstractions |
pyo3_ffi, not pyo3The low-level pyo3_ffi module is used, not pyo3 directly.
This avoids overhead, complex abstractions, and gives full control over generated code.
The src/py/ module provides safe wrappers. Key types:
| Type | Purpose |
|---|---|
PyObj | Core wrapper around *mut PyObject. Has .extract() (Copy types), .extract_ref() (ref types), .type_(), .is_none() |
Owned<T> | RAII refcount wrapper. Use Owned::new() to take ownership, .borrow() for non-owning access |
PyClass<T> | A Python class whose instances contain a Rust T; carries module state via .state() → &State |
PyRef<'a, T> | A borrowed extension instance together with access to its T payload |
PyPayload | Trait implemented by Rust values stored inside extension objects |
PyType | A Python type object |
PyReturn | Alias for PyResult<Owned<PyObj>> — the return type of Python-visible functions |
PyErrMarker | Sentinel indicating the Python error indicator is set |
Key helpers in src/py/:
raise_value_err(), raise_type_err(), raise_key_err() — raise Python exceptionswarn_with_class(cls, msg, stacklevel) — emit a Python warning. Takes PyObj, not a raw pointerhandle_kwargs(fname, kwargs, handler) — iterate kwargs with interned string matchinghandle_no_args(fname, args) — reject positional argumentshandle_one_arg(fname, args) — extract exactly one positional arg, or raise TypeErrorhandle_opt_arg(fname, args) — extract zero or one positional arghandle_one_kwarg(fname, key, kwargs) — extract a single optional kwarg by keyraise_mixed_args(fname), raise_unexpected_kwarg(fname, key) — keep common argument errors
consistent in class-specific parsersobj.expect_int(name) — accept a Python int or subclass and raise
TypeError: {name} must be an integer otherwisefind_interned(value, &[(string, value), ...]) — match a PyObj against an
interned-string/value table, returning Optionmatch_interned_str(name, value, &[(string, value), ...]) — like find_interned but
raises on no matchfind_interned_with(value, handler) — compose multiple interned-string matchers while
retaining one global pointer-equality pass before Unicode comparisonfind_interned_by(value, choices, eq) — match one table using the equality function supplied
by find_interned_withmatch_type!(obj, type => |value| {...}, _ => {...}) — match an extension object against differently typed PyClass<T> values; prefix an arm with ref for non-Copy typesCompareOp::from_ffi(op).apply(a, b) — apply a CPython rich-comparison operation to ordered Rust valuesgeneric_alloc(cls, data) — allocate a Python object with the given payloadPyAsciiStrBuilder::format() — build a Python string without intermediate Rust StringPyTuple::with_len() / unsafe .init_item_unchecked() — allocate and initialize tuple slotsState (in src/pymodule/def.rs) is a large struct stored on the Python module. It holds:
HeapType<T> for each class (date_type, time_delta_type, etc.)exc_repeated, exc_skipped, etc.)warn_deprecation, warn_days_not_always_24h, etc.)str_years, str_hour, str_units, etc.)Access it via cls.state() from any PyClass<T>.
Methods are registered in a static mut METHODS: &[PyMethodDef] array using macros:
method0! — no argsmethod1! — one positional argmethod_vararg! — variable positional argsmethod_kwargs! — positional args + keyword argsclassmethod1!, classmethod_kwargs! — class methodsThe function signatures must match the macro used. For method_kwargs!:
fn my_method(cls: PyClass<MyType>, slf: MyType, args: &[PyObj], kwargs: &mut IterKwargs) -> PyReturn
PyAsciiStrBuilder instead of format!() → to_py())i32/i64 over i128 when possiblePositional argument handling:
// No positional args:
handle_no_args("method_name", args)?;
// Exactly one required arg:
let arg = handle_one_arg("method_name", args)?;
// Zero or one optional arg:
let maybe_arg = handle_opt_arg("method_name", args)?;
Kwarg handling:
handle_kwargs("method_name", kwargs, |key, value, eq| {
if eq(key, str_some_kwarg) {
// parse value
} else {
return Ok(false); // unrecognized kwarg
}
Ok(true)
})
Single optional kwarg shortcut:
let relative_to = handle_one_kwarg("total", state.str_relative_to, kwargs)?;
Building deltas from kwargs (shift/add/subtract methods):
Use common::shift_args::parse_datetime_shift_kwargs() for full datetime units or
parse_calendar_shift_kwargs() for calendar-only units. They return a typed
DateTimeShift or CalendarShift; the datetime parser's callback retains
class-specific kwargs such as disambiguate and warning suppression. For a
positional delta, use parse_datetime_shift_arg() or parse_calendar_shift_arg();
these raise the method-specific type error if the argument is not a supported delta.
Instant-like arguments:
Use common::instant::extract_instant() when a non-Instant operand should fall through to another
operation, and parse_instant_arg() for a required Instant, OffsetDateTime, or ZonedDateTime
argument. Both normalize to the domain Instant.
Interned string matching with custom errors:
Use find_interned + manual error message when you need a specific error format.
Use match_interned_str when the default error format is acceptable.
For composed subsets, use find_interned_with(value, |v, eq| ...), call
find_interned_by(v, choices, eq) for table-shaped subsets, and use eq(v, expected) for
individual strings. Do not call top-level find_interned separately for each subset: that would
perform Unicode comparison before later subsets have had their pointer-equality pass.
Error handling:
raise_value_err("msg")? for ValueError.ok_or_value_err("msg")? on Options — for domain errors with specific messages.ok_or_range_err()? on Options — for generic out-of-range errors (preferred)PyErrMarker() (with parens) as the sentinel in PyResult<T>Ord in Rust. Compare via .to_instant() for ordering.
Non-Copy (contains Arc<TimeZone>). Uses Arc::ptr_eq + content equality for timezone comparison.
DST-aware operations resolve PlainDateTime::local_seconds() through
TimeZone::mapping_for_local() and PlainDateTime::resolve_in().EpochSecs to local timezone
lookup. LocalMapping, Disambiguation, and ResolvePolicy define gap/fold handling in the
domain layer. Map ResolveError to Python exceptions only in binding code.Instant has Ord). Offset is an Offset scalar.PlainDateTime::assume_offset() when attaching a validated offset and
assume_offset_unchecked() only when the represented instant is already known to be in range.Ord.CalendarShift and DateTimeShift in the domain layer. Prefer
Date::shift_by() and PlainDateTime::shift_by() once a shift has been parsed. Python-facing
component replacement starts with PlainDateTime::components() and uses
DateTimeComponents in classes::plain_datetime.secs: DeltaSeconds + subsec: SubSecNanos. Use .total_nanos() -> i128.
Has .in_single_unit() and .in_exact_units() for unit decomposition, and owns the pure
parse_iso() implementation; map its parse errors to Python exceptions in the binding.fmt::Precision, round::RoundUnit, and
CalendarUnit/DifferenceUnit/ExactUnit in domain::difference. Keep these domains distinct
unless their parsing and behavior are demonstrably identical. Keep free of Python
argument parsing; adapts Python arguments to its pure types..ok_or_range_err() for out-of-range errors instead of custom messages.// SAFETY: comments for unsafe blocks per the Rust convention (exact casing matters).pub(crate) not pub for internal visibility..to_py() via the ToPy trait — convert Rust values to Python objects.to_tuple() — convert a Python sequence to a tuple (prefer over seq_len+seq_getitem)import(module_name) — import a Python module (don't call PyImport_ImportModule directly)common::fmtcommon::format_argsformat_isoDeltaField<T> with the integer type's MIN value as
the UNSET sentinel. DeltaField has custom Debug showing <unset> for sentinel values and
.as_option() for checked extraction.基于 SOC 职业分类