Skip to content

Commit c7d6bd4

Browse files
authored
feat: check for ProxiedPyObject symbol when converting object (denosaurs#19)
1 parent 6769178 commit c7d6bd4

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

src/python.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22
import { py } from "./ffi.ts";
33
import { cstr } from "./util.ts";
44

5+
/**
6+
* Symbol used on proxied Python objects to point to the original PyObject object.
7+
* Can be used to implement PythonProxy and create your own proxies.
8+
*
9+
* See `PyObject#proxy` for more info on proxies.
10+
*/
11+
export const ProxiedPyObject = Symbol("ProxiedPyObject");
12+
13+
/**
14+
* Proxied Python object.
15+
*
16+
* When an object implements this interface, the object will not be converted
17+
* to a Python Object and the original PyObject will be used.
18+
*/
19+
export interface PythonProxy {
20+
[ProxiedPyObject]: PyObject;
21+
}
22+
523
/**
624
* JS types that can be converted to Python Objects.
725
*
@@ -30,6 +48,8 @@ import { cstr } from "./util.ts";
3048
* - `Set` becomes `set` in Python.
3149
*
3250
* If you pass a PyObject, it is used as-is.
51+
*
52+
* If you pass a PythonProxy, its original PyObject will be used.
3353
*/
3454
export type PythonConvertible =
3555
| number
@@ -41,18 +61,12 @@ export type PythonConvertible =
4161
| string
4262
// deno-lint-ignore ban-types
4363
| Symbol
64+
| PythonProxy
4465
| PythonConvertible[]
4566
| { [key: string]: PythonConvertible }
4667
| Map<PythonConvertible, PythonConvertible>
4768
| Set<PythonConvertible>;
4869

49-
/**
50-
* Symbol used on proxied Python objects to point to the original PyObject object.
51-
*
52-
* See `PyObject#proxy` for more info on proxies.
53-
*/
54-
export const ProxiedPyObject = Symbol("ProxiedPyObject");
55-
5670
/**
5771
* An argument that can be passed to PyObject calls to indicate that the
5872
* argument should be passed as a named one.
@@ -310,6 +324,9 @@ export class PyObject {
310324
case "object": {
311325
if (v === null) {
312326
return python.builtins.None[ProxiedPyObject];
327+
} else if (ProxiedPyObject in v) {
328+
const proxy = v as PythonProxy;
329+
return proxy[ProxiedPyObject];
313330
} else if (Array.isArray(v)) {
314331
const list = py.PyList_New(v.length) as Deno.UnsafePointer;
315332
for (let i = 0; i < v.length; i++) {

test/test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { assert, assertEquals } from "./deps.ts";
2-
import { NamedArgument, PyObject, python } from "../mod.ts";
2+
import {
3+
NamedArgument,
4+
ProxiedPyObject,
5+
PyObject,
6+
python,
7+
PythonProxy,
8+
} from "../mod.ts";
39

410
const { version, executable } = python.import("sys");
511
console.log("Python version:", version);
@@ -146,3 +152,22 @@ class Test:
146152
Deno.test("numpy", () => {
147153
const _np = python.import("numpy");
148154
});
155+
156+
Deno.test("custom proxy", () => {
157+
const np = python.import("numpy");
158+
159+
// We declare our own PythonProxy wrapper
160+
const CustomProxy = class implements PythonProxy {
161+
public readonly [ProxiedPyObject]: PyObject;
162+
163+
constructor(array: PythonProxy) {
164+
this[ProxiedPyObject] = array[ProxiedPyObject];
165+
}
166+
};
167+
168+
// Wrap the result in our custom wrapper
169+
const arr = new CustomProxy(np.array([1, 2, 3]));
170+
171+
// Then, we use the wrapped proxy as if it were an original PyObject
172+
assertEquals(np.add(arr, 2).tolist().valueOf(), [3, 4, 5]);
173+
});

0 commit comments

Comments
 (0)