Skip to content

Commit

Permalink
Refactor bindgen (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
zshipko authored Nov 22, 2024
1 parent 4828a02 commit d20fe79
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 82 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jobs:
- name: Run Bindgen Test
run: |
sh bundle.sh
sh bindgen-test.sh install
sh bindgen-test.sh run
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"typescript": "^5.3.2"
},
"dependencies": {
"@dylibso/xtp-bindgen": "1.0.0-rc.11",
"@dylibso/xtp-bindgen": "1.0.0-rc.13",
"ejs": "^3.1.10"
}
}
126 changes: 72 additions & 54 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,77 @@
import ejs from "ejs";
import { getContext, helpers, Property } from "@dylibso/xtp-bindgen";
import {
helpers,
getContext,
ObjectType,
EnumType,
ArrayType,
XtpNormalizedType,
XtpTyped
} from "@dylibso/xtp-bindgen"

function toPythonType(property: Property): string {
let tp
function pythonTypeName(s: string): string {
return helpers.snakeToPascalCase(s);
}

function pythonFunctionName(s: string): string {
return helpers.camelToSnakeCase(s);
}

function isOptional(type: String): boolean {
return type.startsWith('Optional[')
}

if (property.$ref) {
tp = property.$ref.name
} else {
switch (property.type) {
case "string":
if (property.format === "date-time") {
tp = "datetime"
} else {
tp = "str"
}
break
case "number":
// @ts-ignore
if (property.contentType === "application/json") {
tp = "str"
} else if (property.format === "float" || property.format === "double") {
tp = "float"
} else {
tp = "int"
}
break
case "integer":
// @ts-ignore
if (property.contentType === "application/json") {
tp = "str"
} else {
tp = "int"
}
break
case "boolean":
tp = "bool"
break
case "object":
tp = "dict"
break
case "array":
if (!property.items) {
tp = "list"
} else {
tp = `List[${toPythonType(property.items as Property)}]`
}
break
case "buffer":
tp = "bytes"
break
default:
throw new Error("Can't convert property to Python type: " + property.type);
}
function toPythonTypeX(type: XtpNormalizedType): string {
const opt = (t: string) => {
return type.nullable ? `Optional[${t}]` : t
}
switch (type.kind) {
case 'string':
return opt('str');
case 'int32':
return opt('int');
case 'float':
return opt('float');
case 'double':
return opt('float')
case 'byte':
return opt('byte');
case 'date-time':
return opt("datetime");
case 'boolean':
return opt('bool');
case 'array':
const arrayType = type as ArrayType
return opt(`List[${toPythonTypeX(arrayType.elementType)}]`);
case 'buffer':
return opt('bytes');
case 'map':
// TODO: improve typing of dicts
return opt('dict');
case 'object':
return opt(pythonTypeName((type as ObjectType).name));
case 'enum':
return opt(pythonTypeName((type as EnumType).name));
default:
throw new Error("Can't convert XTP type to Python type: " + type)
}
}


function toPythonType(property: XtpTyped): string {
let t = toPythonTypeX(property.xtpType);
if (isOptional(t)) return t;
return `Optional[${t}]`;
}

if (!tp) throw new Error("Cant convert property to Python type: " + property.type)
if (!property.nullable && !property.required) return tp
return `Optional[${tp}]`
function toPythonParamType(property: XtpTyped): string {
let t = toPythonTypeX(property.xtpType);
// We need to represent bare int/float as a str in Python for now,
// there may be some updates to the encoder we can make to handle
// this case at some point
t = t.replace('int', 'str');
t = t.replace('float', 'str');
return t;
}

export function render() {
Expand All @@ -65,6 +80,9 @@ export function render() {
...helpers,
...getContext(),
toPythonType,
toPythonParamType,
pythonTypeName,
pythonFunctionName
};

const output = ejs.render(tmpl, ctx);
Expand Down
12 changes: 7 additions & 5 deletions template/plugin/__init__.py.ejs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# THIS FILE WAS GENERATED BY `xtp-python-bindgen`. DO NOT EDIT.

from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
import extism # pyright: ignore
import plugin

<% if (Object.values(schema.schemas).length > 0){ %>
from pdk_types import <%- Object.values(schema.schemas).map(schema => schema.name).join(", ") %> # noqa: F401
from pdk_types import <%- Object.values(schema.schemas).map(schema => pythonTypeName(schema.name)).join(", ") %> # noqa: F401
<% } %>

# Imports
Expand All @@ -13,13 +15,13 @@ from pdk_types import <%- Object.values(schema.schemas).map(schema => schema.nam
<% if (hasComment(imp)) -%>
#<%- imp.name %> <%- formatCommentBlock(imp.description, "# ") %>
<% if (hasComment(imp.input)) { -%>
# It takes input of <%- toPythonType(imp.input) %> (<%- formatCommentLine(imp.input.description) %>)
# It takes input of <%- toPythonParamType(imp.input) %> (<%- formatCommentLine(imp.input.description) %>)
<% } -%>
<% if (hasComment(imp.output)) { -%>
# And it returns an output <%- toPythonType(imp.output) %> (<%- formatCommentLine(imp.output.description) %>)
# And it returns an output <%- toPythonParamType(imp.output) %> (<%- formatCommentLine(imp.output.description) %>)
<% } -%>
@extism.import_fn("extism:host/user", "<%- imp.name %>")
def <%- camelToSnakeCase(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonType(imp.output) %><% } %>: # pyright: ignore [reportReturnType]
def <%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonParamType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonParamType(imp.output) %><% } %>: # pyright: ignore [reportReturnType]
pass
<% }) %>

Expand All @@ -32,7 +34,7 @@ def <%- camelToSnakeCase(imp.name) %>(<% if (imp.input) { -%>input: <%- toPython
<% } -%>
@extism.plugin_fn
def <%- ex.name %>():
res = plugin.<%- camelToSnakeCase(ex.name) %>(<% if (ex.input) { %> extism.input(<%- toPythonType(ex.input) %>) <% } %>)
res = plugin.<%- pythonFunctionName(ex.name) %>(<% if (ex.input) { %> extism.input(<%- toPythonParamType(ex.input) %>) <% } %>)
extism.output(res)
<% }) %>
10 changes: 6 additions & 4 deletions template/plugin/pdk_imports.py.ejs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# THIS FILE WAS GENERATED BY `xtp-python-bindgen`. DO NOT EDIT.

from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
import extism # noqa: F401 # pyright: ignore

<% if (Object.values(schema.schemas).length > 0) { %>
from pdk_types import <%- Object.values(schema.schemas).map(schema => schema.name).join(", ") %> # noqa: F401
from pdk_types import <%- Object.values(schema.schemas).map(schema => pythonTypeName(schema.name)).join(", ") %> # noqa: F401
<% } %>

<% schema.imports.forEach(imp => { %>
<% if (hasComment(imp)) -%>
#<%- imp.name %> <%- formatCommentBlock(imp.description, "# ") %>
<% if (hasComment(imp.input)) { -%>
# It takes input of <%- toPythonType(imp.input) %> (<%- formatCommentLine(imp.input.description) %>)
# It takes input of <%- toPythonParamType(imp.input) %> (<%- formatCommentLine(imp.input.description) %>)
<% } -%>
<% if (hasComment(imp.output)) { -%>
# And it returns an output <%- toPythonType(imp.output) %> (<%- formatCommentLine(imp.output.description) %>)
# And it returns an output <%- toPythonParamType(imp.output) %> (<%- formatCommentLine(imp.output.description) %>)
<% } -%>
@extism.import_fn("extism:host/user", "<%- imp.name %>")
def <%- camelToSnakeCase(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonType(imp.output) %><% } %>: # pyright: ignore [reportReturnType]
def <%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonParamType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonParamType(imp.output) %><% } %>: # pyright: ignore [reportReturnType]
pass
<% }) %>

Expand Down
22 changes: 11 additions & 11 deletions template/plugin/pdk_types.py.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ import extism # noqa: F401 # pyright: ignore

<% Object.values(schema.schemas).forEach(schema => { %>
<% if (schema.enum) { %>
class <%- capitalize(schema.name) %>(Enum):
class <%- pythonTypeName(schema.name) %>(Enum):
<% schema.enum.forEach(variant => { -%>
<%- capitalize(variant) %> = "<%- variant %>"
<%- pythonTypeName(variant) %> = "<%- variant %>"
<% }) %>
<% } else { %>
@dataclass
class <%- capitalize(schema.name) %>(extism.Json):
class <%- pythonTypeName(schema.name) %>(extism.Json):
<% schema.properties.forEach(p => { -%>
<% if (p.description) { -%>
# <%- formatCommentBlock(p.description, "# ") %>
<% } -%>
<% if (!p.nullable) {%>
<% if (!p.nullable || p.required) {%>
<% if (p.description) { -%>
# <%- formatCommentBlock(p.description, "# ") %>
<% } -%>
<%- p.name %>: <%- toPythonType(p) %>
<% } %>
<% }) %>
<% schema.properties.forEach(p => { -%>
<% if (p.description) { -%>
# <%- formatCommentBlock(p.description, "# ") %>
<% } -%>
<% if (p.nullable) {%>
<% if (p.nullable && !p.required) {%>
<% if (p.description) { -%>
# <%- formatCommentBlock(p.description, "# ") %>
<% } -%>
<%- p.name %>: <%- toPythonType(p) %> = None
<% } %>
<% }) %>
Expand Down
9 changes: 6 additions & 3 deletions template/plugin/plugin.py.ejs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
import extism # noqa: F401 # pyright: ignore

<% if (Object.values(schema.schemas).length > 0) { %>
from pdk_types import <%- Object.values(schema.schemas).map(schema => schema.name).join(", ") %> # noqa: F401
from pdk_types import <%- Object.values(schema.schemas).map(schema => pythonTypeName(schema.name)).join(", ") %> # noqa: F401
<% } %>
<% if (schema.imports.length > 0) { %>
from pdk_imports import <%- schema.imports.map(schema => camelToSnakeCase(schema.name)).join(", ") %> # noqa: F401
from pdk_imports import <%- schema.imports.map(schema => pythonFunctionName(schema.name)).join(", ") %> # noqa: F401
<% } %>
from typing import List, Optional # noqa: F401

<% schema.exports.forEach(ex => { -%>
<% if (hasComment(ex)) { -%>
# <%- formatCommentBlock(ex.description, "# ") %>
<% } -%>
def <%- camelToSnakeCase(ex.name) %>(<% if (ex.input) { %>input: <%- toPythonType(ex.input) %> <% } %>) <% if (ex.output) {%>-> <%- toPythonType(ex.output) %><%}%>:
def <%- pythonFunctionName(ex.name) %>(<% if (ex.input) { %>input: <%- toPythonParamType(ex.input) %> <% } %>) <% if (ex.output) {%>-> <%- toPythonParamType(ex.output) %><%}%>:
<% if (featureFlags['stub-with-code-samples'] && codeSamples(ex, 'python').length > 0) { -%>
<%- codeSamples(ex, 'python')[0].source %>
<% } else { -%>
Expand Down

0 comments on commit d20fe79

Please sign in to comment.