diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c65a205..adc9502 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,12 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup latest deno version - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x + uses: denoland/setup-deno@v2 - name: Run deno fmt run: deno fmt --check @@ -28,31 +26,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup latest deno version - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x + uses: denoland/setup-deno@v2 - name: Run deno task check run: deno task check - test: name: test ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest, ubuntu-latest, macos-latest,] + os: [windows-latest, ubuntu-latest, macos-latest] steps: - name: Checkout sources uses: actions/checkout@v2 - name: Setup latest deno version - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x + uses: denoland/setup-deno@v2 - name: Setup Bun if: ${{ matrix.os != 'windows-latest' }} @@ -62,7 +55,7 @@ jobs: uses: actions/setup-python@v2 if: ${{ matrix.os == 'windows-latest' }} with: - python-version: '3.13' + python-version: "3.13" - name: Install NumPy if: ${{ matrix.os != 'macos-latest' }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..24b240b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,22 @@ +name: Publish + +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Setup latest deno version + uses: denoland/setup-deno@v2 + + - name: Publish to JSR + run: deno publish diff --git a/README.md b/README.md index 10b48ca..6c583c0 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,29 @@ the Python dynamic library, which is like `python310.dll` (Windows), `libpython310.dylib` (macOS) and `libpython310.so` (Linux) depending on platform. +## Usage with docker + +Usage with docker is easiest done using the +[`denoland/deno:bin` image](https://github.com/denoland/deno_docker?tab=readme-ov-file#using-your-own-base-image) +along with the [official `python` image](https://hub.docker.com/_/python/). + +```Dockerfile +ARG DENO_VERSION=1.38.2 +ARG PYTHON_VERSION=3.12 + +FROM denoland/deno:bin-$DENO_VERSION AS deno +FROM python:$PYTHON_VERSION + +# Copy and configure deno +COPY --from=deno /deno /usr/local/bin/deno +ENTRYPOINT ["/usr/local/bin/deno"] + +# Copy your project source +COPY . . + +RUN ["run", "-A", "--unstable", "https://deno.land/x/python@0.4.2/examples/hello_python.ts"] +``` + ## Maintainers - DjDeveloper ([@DjDeveloperr](https://github.com/DjDeveloperr)) diff --git a/deno.json b/deno.json index 500bf91..571d1e7 100644 --- a/deno.json +++ b/deno.json @@ -1,4 +1,10 @@ { + "name": "@denosaurs/python", + "version": "0.4.4", + "exports": { + ".": "./mod.ts", + "./ext/pip": "./ext/pip.ts" + }, "tasks": { "check": "deno task check:mod && deno task check:ext && deno task check:examples", "check:mod": "deno check --unstable-ffi mod.ts", diff --git a/ext/pip.ts b/ext/pip.ts index d5c4dd4..3f4e8bd 100644 --- a/ext/pip.ts +++ b/ext/pip.ts @@ -118,7 +118,8 @@ export class Pip { * * ``` */ - async import(module: string, entrypoint?: string) { + // deno-lint-ignore no-explicit-any + async import(module: string, entrypoint?: string): Promise { const { name } = getModuleNameAndVersion(module); await this.install(module); @@ -166,5 +167,5 @@ export class Pip { } } -export const pip = new Pip(); +export const pip: Pip = new Pip(); export default pip; diff --git a/ipy.ts b/ipy.ts index 089a16b..4fed1c3 100644 --- a/ipy.ts +++ b/ipy.ts @@ -1,5 +1,5 @@ -import py, { Python } from "./mod.ts"; -import { Pip, pip } from "./ext/pip.ts"; +import py, { type Python } from "./mod.ts"; +import { type Pip, pip } from "./ext/pip.ts"; declare global { const py: Python; diff --git a/package.json b/package.json index 291f790..27063ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bunpy", - "version": "0.3.3", + "version": "0.4.4", "description": "JavaScript -> Python Bridge for Deno and Bun", "main": "mod.bun.ts", "directories": { diff --git a/src/python.ts b/src/python.ts index bc05479..0b39e4a 100644 --- a/src/python.ts +++ b/src/python.ts @@ -138,7 +138,10 @@ export function kw( * ``` */ export class Callback { - unsafe; + unsafe: Deno.UnsafeCallback<{ + parameters: ["pointer", "pointer", "pointer"]; + result: "pointer"; + }>; constructor(public callback: PythonJSCallback) { this.unsafe = new Deno.UnsafeCallback( @@ -200,7 +203,7 @@ export class PyObject { /** * Check if the object is NULL (pointer) or None type in Python. */ - get isNone() { + get isNone(): boolean { // deno-lint-ignore ban-ts-comment // @ts-expect-error return this.handle === null || this.handle === 0 || @@ -403,9 +406,17 @@ export class PyObject { /** * Performs an equals operation on the Python object. */ - equals(rhs: PythonConvertible) { + equals(rhs: PythonConvertible): boolean { const rhsObject = PyObject.from(rhs); - return py.PyObject_RichCompareBool(this.handle, rhsObject.handle, 3); + const comparison = py.PyObject_RichCompareBool( + this.handle, + rhsObject.handle, + 3, + ); + if (comparison === -1) { + maybeThrowError(); + } + return comparison === 1; } /** @@ -590,7 +601,7 @@ export class PyObject { /** * Tries to set the attribute, throws an error otherwise. */ - setAttr(name: string, v: PythonConvertible) { + setAttr(name: string, v: PythonConvertible): void { if ( py.PyObject_SetAttrString( this.handle, @@ -603,35 +614,35 @@ export class PyObject { } /** Checks if Python object has an attribute of given name. */ - hasAttr(attr: string) { + hasAttr(attr: string): boolean { return py.PyObject_HasAttrString(this.handle, cstr(attr)) !== 0; } /** * Casts a Bool Python object as JS Boolean value. */ - asBoolean() { + asBoolean(): boolean { return py.PyLong_AsLong(this.handle) === 1; } /** * Casts a Int Python object as JS Number value. */ - asLong() { + asLong(): number { return py.PyLong_AsLong(this.handle) as number; } /** * Casts a Float (Double) Python object as JS Number value. */ - asDouble() { + asDouble(): number { return py.PyFloat_AsDouble(this.handle) as number; } /** * Casts a String Python object as JS String value. */ - asString() { + asString(): string | null { const str = py.PyUnicode_AsUTF8(this.handle); return str !== null ? Deno.UnsafePointerView.getCString(str) : null; } @@ -639,7 +650,7 @@ export class PyObject { /** * Casts a List Python object as JS Array value. */ - asArray() { + asArray(): PythonConvertible[] { const array: PythonConvertible[] = []; for (const i of this) { array.push(i.valueOf()); @@ -653,7 +664,7 @@ export class PyObject { * Note: `from` supports converting both Map and Object to Python Dict. * But this only supports returning a Map. */ - asDict() { + asDict(): Map { const dict = new Map(); const keys = py.PyDict_Keys(this.handle); const length = py.PyList_Size(keys) as number; @@ -669,7 +680,7 @@ export class PyObject { return dict; } - *[Symbol.iterator]() { + *[Symbol.iterator](): Generator { const iter = py.PyObject_GetIter(this.handle); let item = py.PyIter_Next(iter); while (item !== null) { @@ -682,8 +693,8 @@ export class PyObject { /** * Casts a Set Python object as JS Set object. */ - asSet() { - const set = new Set(); + asSet(): Set { + const set = new Set(); for (const i of this) { set.add(i.valueOf()); } @@ -693,7 +704,7 @@ export class PyObject { /** * Casts a Tuple Python object as JS Array value. */ - asTuple() { + asTuple(): PythonConvertible[] { const tuple = new Array(); const length = py.PyTuple_Size(this.handle) as number; for (let i = 0; i < length; i++) { @@ -711,7 +722,7 @@ export class PyObject { * Only primitives are casted as JS value type, otherwise returns * a proxy to Python object. */ - valueOf() { + valueOf(): any { const type = py.PyObject_Type(this.handle); if (Deno.UnsafePointer.equals(type, python.None[ProxiedPyObject].handle)) { @@ -759,7 +770,7 @@ export class PyObject { call( positional: (PythonConvertible | NamedArgument)[] = [], named: Record = {}, - ) { + ): PyObject { // count named arguments const namedCount = positional.filter( (arg) => arg instanceof NamedArgument, @@ -808,16 +819,16 @@ export class PyObject { /** * Returns `str` representation of the Python object. */ - toString() { + toString(): string { return new PyObject(py.PyObject_Str(this.handle)) - .asString(); + .asString()!; } - [Symbol.for("Deno.customInspect")]() { + [Symbol.for("Deno.customInspect")](): string { return this.toString(); } - [Symbol.for("nodejs.util.inspect.custom")]() { + [Symbol.for("nodejs.util.inspect.custom")](): string { return this.toString(); } } @@ -928,7 +939,7 @@ export class Python { /** * Runs Python script from the given string. */ - run(code: string) { + run(code: string): void { if (py.PyRun_SimpleString(cstr(code)) !== 0) { throw new EvalError("Failed to run python code"); } @@ -938,7 +949,7 @@ export class Python { * Runs Python script as a module and returns its module object, * for using its attributes, functions, classes, etc. from JavaScript. */ - runModule(code: string, name?: string) { + runModule(code: string, name?: string): any { const module = py.PyImport_ExecCodeModule( cstr(name ?? "__main__"), PyObject.from( @@ -956,7 +967,7 @@ export class Python { /** * Import a module as PyObject. */ - importObject(name: string) { + importObject(name: string): PyObject { const mod = py.PyImport_ImportModule(cstr(name)); if (mod === null) { maybeThrowError(); @@ -968,7 +979,7 @@ export class Python { /** * Import a Python module as a proxy object. */ - import(name: string) { + import(name: string): any { return this.importObject(name).proxy; } @@ -1013,7 +1024,7 @@ export class Python { * and also make use of some common built-ins attached to * this object, such as `str`, `int`, `tuple`, etc. */ -export const python = new Python(); +export const python: Python = new Python(); /** * Returns true if the value can be converted into a Python slice or diff --git a/test/test.ts b/test/test.ts index bed7018..796a159 100644 --- a/test/test.ts +++ b/test/test.ts @@ -5,7 +5,7 @@ import { ProxiedPyObject, PyObject, python, - PythonProxy, + type PythonProxy, } from "../mod.ts"; const { version, executable } = python.import("sys");