Skip to content

Add Cython .pxd declarations and Python type stubs - #56

Open
wiese-m wants to merge 3 commits into
TeskaLabs:mainfrom
wiese-m:main
Open

wiese-m wants to merge 3 commits into
TeskaLabs:mainfrom
wiese-m:main

Conversation

@wiese-m

@wiese-m wiese-m commented Apr 5, 2026 •

Copy link
Copy Markdown

Summary

  • Add cysimdjson.pxd with full Cython declarations for cimport support
  • Add cysimdjson.pyi type stubs for Python
  • Add py.typed PEP 561 marker
  • Change key methods from def to cpdef (at_pointer, get_value, export, parse, parse_in_place, parse_string) to enable C-level dispatch from Cython
  • Fix incorrect self type annotations on JSONObject.get_value/export and JSONArray.get_value/export (were JSONElement, should be their own class)
  • Ship .pxd, .pyi, and py.typed via package_data in setup.py

Motivation

This enables downstream Cython projects to cimport cysimdjson types directly:

from cysimdjson.cysimdjson cimport JSONParser, simdjson_parser, simdjson_element

This unlocks:

  • Using simdjson_parser as a C++ field (zero Python overhead)
  • C-level dispatch on cpdef methods (parse, at_pointer)
  • Typed cdef fields in consuming cdef class definitions

Closes #49

Summary by CodeRabbit

  • New Features

    • Public JSON API classes (object/array/element/parser) now expose parsing and navigation methods (parse, parse_in_place, parse_string, at_pointer, get_value, export).
    • Module-level constants and a helper to map addresses to elements added for inspection and traversal.
  • Chores

    • Included typing stubs and Cython interface declarations in the package to improve typing, IDE autocomplete, and distribution.

@coderabbitai

coderabbitai Bot commented Apr 5, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea239aeb-e392-4ea1-8c81-b101125fc708

📥 Commits

Reviewing files that changed from the base of the PR and between 76bddfd and 0eac961.

📒 Files selected for processing (1)
  • cysimdjson/cysimdjson.pxd

📝 Walkthrough

Walkthrough

Adds new Cython interface (.pxd) and Python type stubs (.pyi), updates the Cython implementation (.pyx) to export cpdef APIs, and includes the new typing/interface files in package data.

Changes

Cohort / File(s) Summary
Cython interface (.pxd)
cysimdjson/cysimdjson.pxd
New Cython declaration file exposing simdjson C++ DOM types, constants, enums, an error handler, and cdef class signatures for JSONObject, JSONArray, JSONElement, and JSONParser with cpdef method signatures.
Python type stubs (.pyi)
cysimdjson/cysimdjson.pyi
New typing stub declaring public API: JSONObject, JSONArray, JSONElement, JSONParser, addr_to_element, and module constants (MAXSIZE_BYTES, PADDING, SIMDJSON_VERSION) with return/type annotations.
Cython implementation (.pyx)
cysimdjson/cysimdjson.pyx
Removed many inline/extern C++ bindings (moved to .pxd), converted multiple methods from def to cpdef object for container/element/parser APIs, and removed some class-level cdef attribute declarations while preserving behavior.
Packaging
setup.py
Added py.typed, cysimdjson.pxd, and cysimdjson.pyi to package_data so typing and Cython declaration files are distributed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I dug through headers, nibbling bytes with glee,
I sewed pxd threads and wrote a stubbery.
cpdef hops made functions quick to find,
IDE carrots gleam — tidy types for humankind.
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Cython .pxd declarations and Python type stubs' accurately summarizes the main changes: addition of .pxd Cython declarations and .pyi Python type stubs.
Linked Issues check ✅ Passed The PR fully addresses issue #49 by adding Python type stubs (.pyi) with comprehensive type annotations for JSONParser, JSONObject, JSONArray, and JSONElement classes, plus module constants.
Out of Scope Changes check ✅ Passed All changes are in scope: .pxd and .pyi files support type stubs and cimport functionality, cpdef conversions enable C-level dispatch for documented methods, and setup.py changes ship the new files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cysimdjson/cysimdjson.pyx (2)

295-307: ⚠️ Potential issue | 🔴 Critical

parse_in_place() cannot safely accept a plain bytes buffer.

When realloc_if_needed=0 is used at line 306, simdjson requires SIMDJSON_PADDING (64) readable bytes beyond the JSON payload. PyBytes_AsStringAndSize() provides only the internal Python bytes buffer, which guarantees only len+1 null-terminated bytes. A plain bytes object cannot satisfy this precondition. The docstring acknowledges this requirement ("you have to ensure proper padding"), but lacks runtime validation, leaving callers vulnerable to undefined behavior.

Either enforce padding with validation/documentation for caller-managed buffers or use the copying parse path (realloc_if_needed=1).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cysimdjson/cysimdjson.pyx` around lines 295 - 307, parse_in_place accepts a
plain bytes buffer but calls self.Parser.parse(..., realloc_if_needed=0) which
requires SIMDJSON_PADDING bytes beyond payload; validate or avoid unsafe
behavior by checking the input buffer padding and raising if insufficient, or
switch to the safe copy path (use realloc_if_needed=1) when the buffer isn’t
guaranteed padded. In practice update parse_in_place (function name
JSONParser.parse usage) to compute the Python bytes length from
PyBytes_AsStringAndSize, verify there are at least SIMDJSON_PADDING readable
bytes after the JSON data (or document/require callers to pass a padded
memoryview/bytearray), and if not either raise a RuntimeError explaining the
padding requirement or call the parser with realloc_if_needed=1 to copy to a
padded buffer before parsing.

170-190: ⚠️ Potential issue | 🔴 Critical

Store a reference to the parser in returned wrapper objects to prevent use-after-free.

The wrapper classes (JSONElement, JSONArray, JSONObject) store only borrowed references (simdjson_element/array/object) from the parser's internal memory, but do not hold a reference to the parser itself. According to simdjson's DOM documentation, these references become invalid if the parser is destroyed or if parse/load is called again on the same parser. This creates a critical safety issue:

# Scenario 1: Parser destruction
parser = JSONParser()
result = parser.parse(b'{"a": 1}')
del parser  # Parser destroyed; result now references invalid memory
value = result.get_value()  # Use-after-free

# Scenario 2: Parser reuse
parser = JSONParser()
result1 = parser.parse(b'{"a": 1}')
result2 = parser.parse(b'{"b": 2}')  # Overwrites result1's data
value = result1.get_value()  # Accesses overwritten memory

To fix this, add a parser reference (either directly or via a parent wrapper) to each returned wrapper at lines 177–190 (from_element), and keep that reference alive throughout the wrapper's lifetime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cysimdjson/cysimdjson.pyx` around lines 170 - 190, The wrapper factory
from_element should attach and retain a reference to the originating parser on
each returned object to prevent use-after-free; update from_element (and its
callers) to capture the parser that produced the simdjson_element and assign it
to a new Parser (or parent) attribute on JSONElement, JSONArray, and JSONObject
(e.g., set new_object.Parser, new_array.Parser, new_element.Parser) so the
parser instance stays alive for the wrapper's lifetime and borrowed
Element/Array/Object references remain valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cysimdjson/cysimdjson.pxd`:
- Around line 32-36: The Cython pxd declares iterator::operator*() returning
simdjson_object but the actual C++ signature returns key_value_pair; update the
declaration for iterator::operator*() to return key_value_pair (not
simdjson_object) so cimported signatures match the real C++ API, and also fix
the array iterator operator++() declaration to include its return type as
iterator operator++() to match the iterator increment signature.
- Around line 56-61: The cppclass declaration for array::iterator is missing an
explicit return type for operator++(), which causes a Cython parse error; update
the declaration of iterator::operator++() to include the correct return type
iterator& (i.e., change the operator++() declaration in the cppclass iterator
block to return iterator&), so it matches simdjson's DOM array iterator and C++
forward iterator semantics.

In `@cysimdjson/cysimdjson.pyi`:
- Line 41: Widen the return type of addr_to_element to match
JSONElement.from_element by returning a union that includes JSONObject and
JSONArray in addition to JSONElement; update the addr_to_element signature
(which forwards to JSONElement.from_element) so type checkers and autocompletion
can infer JSONObject for object nodes and JSONArray for array nodes instead of
always falling back to JSONElement.

---

Outside diff comments:
In `@cysimdjson/cysimdjson.pyx`:
- Around line 295-307: parse_in_place accepts a plain bytes buffer but calls
self.Parser.parse(..., realloc_if_needed=0) which requires SIMDJSON_PADDING
bytes beyond payload; validate or avoid unsafe behavior by checking the input
buffer padding and raising if insufficient, or switch to the safe copy path (use
realloc_if_needed=1) when the buffer isn’t guaranteed padded. In practice update
parse_in_place (function name JSONParser.parse usage) to compute the Python
bytes length from PyBytes_AsStringAndSize, verify there are at least
SIMDJSON_PADDING readable bytes after the JSON data (or document/require callers
to pass a padded memoryview/bytearray), and if not either raise a RuntimeError
explaining the padding requirement or call the parser with realloc_if_needed=1
to copy to a padded buffer before parsing.
- Around line 170-190: The wrapper factory from_element should attach and retain
a reference to the originating parser on each returned object to prevent
use-after-free; update from_element (and its callers) to capture the parser that
produced the simdjson_element and assign it to a new Parser (or parent)
attribute on JSONElement, JSONArray, and JSONObject (e.g., set
new_object.Parser, new_array.Parser, new_element.Parser) so the parser instance
stays alive for the wrapper's lifetime and borrowed Element/Array/Object
references remain valid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e809be8b-0f6d-4906-8c44-6a5dfa628dab

📥 Commits

Reviewing files that changed from the base of the PR and between a86929e and 8dd8547.

📒 Files selected for processing (5)
  • cysimdjson/cysimdjson.pxd
  • cysimdjson/cysimdjson.pyi
  • cysimdjson/cysimdjson.pyx
  • cysimdjson/py.typed
  • setup.py

Comment thread cysimdjson/cysimdjson.pxd Outdated
Comment thread cysimdjson/cysimdjson.pxd
Comment thread cysimdjson/cysimdjson.pyi Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: add Typing / Type Stubs support?

1 participant