Skip to content

Commit 79fb7ab

Browse files
committed
update docs
1 parent f36397a commit 79fb7ab

File tree

10 files changed

+120
-9
lines changed

10 files changed

+120
-9
lines changed

.github/workflows/lint.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
concurrency: lint-${{ github.sha }}
9+
10+
jobs:
11+
lint:
12+
runs-on: ubuntu-latest
13+
14+
env:
15+
PYTHON_VERSION: "3.10"
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v3
20+
21+
- name: Set up Python ${{ env.PYTHON_VERSION }}
22+
uses: actions/setup-python@v3
23+
with:
24+
python-version: ${{ env.PYTHON_VERSION }}
25+
26+
- name: Pre-commit hooks
27+
uses: pre-commit/[email protected]

docs/cpython_bindings.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# CPython ABI Bindings
2+
3+
There is currently limited support for most of the CPython ABI.
4+
5+
The ABI follows the naming convention of `Py<namespace>_<method>`, so you may use one by importing the namespace from pointers.py:
6+
7+
```py
8+
# example for PyEval_* methods
9+
from pointers import PyEval
10+
11+
PyEval.something(...)
12+
```
13+
14+
However, the method names are in `PascalCase`, and according to [PEP 8](https://peps.python.org/pep-0008/), Python functions should be named in `snake_case`.
15+
16+
Since pointers.py is PEP 8 compliant, method names have been converted to snake case.
17+
18+
Here's an example with `PyEval_GetFrame`:
19+
20+
```py
21+
from pointers import PyEval
22+
23+
frame = PyEval.get_frame() # calls the c PyEval_GetFrame function
24+
```
25+
26+
## Casting Pointers
27+
28+
Some functions don't just return `PyObject`, and instead return something that can be casted instead (in this case, `PyFrameObject`):
29+
30+
Any binding that doesn't return a `PyObject*` is simply converted to a `StructPointer`:
31+
32+
```py
33+
from pointers import PyEval
34+
35+
frame = PyEval.get_frame()
36+
# frame is not a frame object, instead its StructPointer[FrameObject]
37+
```
38+
39+
You may cast this pointer to the correct Python object by calling `struct_cast`:
40+
41+
```py
42+
from pointers import PyEval, struct_cast
43+
44+
frame = struct_cast(PyEval.get_frame())
45+
# frame is now a valid frame object!
46+
```
47+
48+
## Limited Support
49+
50+
CPython ABI bindings are still unfinished, and any method that contains one of the following remains unsupported:
51+
52+
- Uses a format string
53+
- Undocumented on [python.org](https://python.org)
54+
- Isn't documented properly

docs/getting_started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ ptr = to_ptr("hello world")
3434
print(*ptr)
3535
```
3636

37+
**Note:** As of now, pointers.py does not officially support Python 3.11 and 3.12
38+
3739
Running this should print `hello world` into the console.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fclose(file)
4949
- Fully type safe
5050
- Pythonic pointer API
5151
- Bindings for the entire C standard library
52-
- Segfaults
52+
- Undefined behaviour
5353

5454
### Why does this exist?
5555

docs/making_a_program.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,6 @@ assert 1 != 2
8686
free(cache)
8787
```
8888

89-
Go ahead and run this to ensure that everything worked fine.
89+
**Note:** This may not work depending on your build of CPython.
9090

9191
Congratulations, you have written a working program with pointers.py!

docs/using_pointers.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,31 @@ ptr = to_ptr(1)
230230
ptr >>= NULL
231231
~ptr # NullPointerError
232232
```
233+
234+
## Handling Segmentation Faults
235+
236+
If you've ever used a language like C or C++, you probably know what a segmentation fault/segfault is.
237+
238+
These can happen when a memory error occurs (e.g. accessing a NULL pointer), and can be annoying to debug in Python.
239+
240+
Luckily, pointers.py has a custom built handler for converting segfaults into Python exceptions.
241+
242+
Here's an example:
243+
244+
```py
245+
from pointers import handle
246+
import ctypes
247+
248+
@handle
249+
def main():
250+
ctypes.string_at(0) # 0 is the same as a NULL address
251+
252+
main()
253+
# instead of python crashing with a segfault, pointers.SegmentViolation error occurs
254+
```
255+
256+
### Pointer Methods
257+
258+
Most pointer methods where a segment violation could occur (`dereference`, `move`, etc.) are decorated with `handle`, so you don't have to worry about manually catching those yourself.
259+
260+
However, methods like `move` can be destructive and cause the error outside of the function (such as when Python does garbage collection), so you may need to make a `main` method and decorate it with `handle` to catch it.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ nav:
66
- Using Pointers: using_pointers.md
77
- Allocation: allocation.md
88
- C Bindings: bindings.md
9+
- CPython ABI Bindings: cpython_bindings.md
910
- Making a Program: making_a_program.md
1011
- Reference: reference.md
1112
theme: readthedocs

src/pointers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
TypedCPointer, VoidPointer, array, cast, to_c_ptr, to_struct_ptr, to_voidp
1616
)
1717
from .calloc import AllocatedArrayPointer, calloc
18-
from .constants import NULL, Nullable, handle, raw_type
18+
from .constants import NULL, Nullable, handle, raw_type, struct_cast
1919
from .custom_binding import binding, binds
2020
from .decay import decay, decay_annotated, decay_wrapped
2121
from .exceptions import (

src/pointers/object_pointer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def move(
4444
size_a: int = sys.getsizeof(deref_a)
4545
size_b: int = sys.getsizeof(deref_b)
4646
refcnt = sys.getrefcount(deref_b)
47+
refcnt_a = sys.getrefcount(deref_a)
4748

4849
if (self._origin_size < size_a) and (not unsafe):
4950
raise InvalidSizeError(
@@ -61,7 +62,7 @@ def move(
6162

6263
self.assign(~data)
6364
ctypes.memmove(bytes_b, bytes_a, len(bytes_a))
64-
set_ref(deref_b, refcnt - 1)
65+
set_ref(deref_b, (refcnt - 1) + (refcnt_a - 2))
6566

6667
@classmethod
6768
def make_from(cls, obj: Nullable[T]) -> "Pointer[T]":

tests/test_pointer.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import ctypes
1+
from ward import raises, test
22

3-
from _pointers import add_ref
4-
from pointers import NULL, InvalidSizeError, Pointer, SegmentViolation
3+
from pointers import NULL, InvalidSizeError, Pointer
54
from pointers import _ as m
6-
from pointers import handle, to_c_ptr, to_ptr
5+
from pointers import to_c_ptr, to_ptr
76
from pointers.exceptions import NullPointerError
8-
from ward import raises, test
97

108

119
@test("creating pointers")

0 commit comments

Comments
 (0)