Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/emitter-framework/src/python/builtins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { SymbolCreator } from "@alloy-js/core";
import { createModule } from "@alloy-js/python";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type dummy = SymbolCreator;

export const datetimeModule = createModule({
name: "datetime",
descriptor: {
".": ["datetime", "date", "time", "timedelta", "timezone"],
},
});

export const decimalModule = createModule({
name: "decimal",
descriptor: {
".": ["Decimal"],
},
});

export const typingModule = createModule({
name: "typing",
descriptor: {
".": ["Any", "NoReturn", "Tuple"],
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, type Children } from "@alloy-js/core";
import { d } from "@alloy-js/core/testing";
import { SourceFile } from "@alloy-js/python";
import type { BasicTestRunner } from "@typespec/compiler/testing";
import { beforeEach, describe, it } from "vitest";
import { Output } from "../../../../src/core/components/output.jsx";
import { createEmitterFrameworkTestRunner } from "../../test-host.js";
import { assertFileContents, compileModelPropertyType, getExternals } from "../../test-utils.js";
import { TypeExpression } from "../type-expression/type-expression.jsx";

let runner: BasicTestRunner;

beforeEach(async () => {
runner = await createEmitterFrameworkTestRunner();
});

function Wrapper(props: { children: Children }) {
return (
<Output program={runner.program} externals={getExternals()}>
<SourceFile path="test.py">{props.children}</SourceFile>
</Output>
);
}

describe("map array expression to Python list", () => {
it.each([["string[]", "list[str]"]])("%s => %s", async (tspType, pythonType) => {
const type = await compileModelPropertyType(tspType, runner);
const res = render(
<Wrapper>
<TypeExpression type={type} />
</Wrapper>,
);

assertFileContents(
res,
d`
${pythonType}
`,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { code } from "@alloy-js/core";
import type { Type } from "@typespec/compiler";
import { TypeExpression } from "../type-expression/type-expression.js";

export interface ArrayExpressionProps {
elementType: Type;
}

export function ArrayExpression({ elementType }: ArrayExpressionProps) {
return code`list[${(<TypeExpression type={elementType} />)}]`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, type Children } from "@alloy-js/core";
import { d } from "@alloy-js/core/testing";
import { SourceFile } from "@alloy-js/python";
import type { BasicTestRunner } from "@typespec/compiler/testing";
import { beforeEach, describe, it } from "vitest";
import { Output } from "../../../../src/core/components/output.jsx";
import { createEmitterFrameworkTestRunner } from "../../test-host.js";
import { assertFileContents, compileModelPropertyType, getExternals } from "../../test-utils.js";
import { TypeExpression } from "../type-expression/type-expression.jsx";

let runner: BasicTestRunner;

beforeEach(async () => {
runner = await createEmitterFrameworkTestRunner();
});

function Wrapper(props: { children: Children }) {
return (
<Output program={runner.program} externals={getExternals()}>
<SourceFile path="test.py">{props.children}</SourceFile>
</Output>
);
}

describe("map Record to Python dict", () => {
it.each([["Record<boolean>", "dict[str, bool]"]])("%s => %s", async (tspType, pythonType) => {
const type = await compileModelPropertyType(tspType, runner);
const res = render(
<Wrapper>
<TypeExpression type={type} />
</Wrapper>,
);

assertFileContents(
res,
d`
${pythonType}
`,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { code } from "@alloy-js/core";
import type { Type } from "@typespec/compiler";
import { TypeExpression } from "../type-expression/type-expression.js";

export interface RecordExpressionProps {
elementType: Type;
}

export function RecordExpression({ elementType }: RecordExpressionProps) {
return code`
dict[str, ${(<TypeExpression type={elementType} />)}]
`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { SourceFile } from "@alloy-js/python";
import type { Namespace } from "@typespec/compiler";
import { describe, expect, it } from "vitest";
import { Output } from "../../../../src/core/components/output.jsx";
import { getProgram } from "../../test-host.js";
import { getExternals } from "../../test-utils.js";
import { TypeAliasDeclaration } from "./type-alias-declaration.jsx";

describe("Python Declaration equivalency to Type Alias", () => {
describe("Type Alias Declaration bound to Typespec Scalar", () => {
describe("Scalar extends utcDateTime", () => {
it("creates a type alias declaration for a utcDateTime without encoding", async () => {
const program = await getProgram(`
namespace DemoService;
scalar MyDate extends utcDateTime;
`);

const [namespace] = program.resolveTypeReference("DemoService");
const scalar = Array.from((namespace as Namespace).scalars.values())[0];

expect(
<Output program={program} externals={getExternals()}>
<SourceFile path="test.py">
<TypeAliasDeclaration type={scalar} />
</SourceFile>
</Output>,
).toRenderTo(`
from datetime import datetime

MyDate: datetime`);
});

it("creates a type alias declaration with JSDoc", async () => {
const program = await getProgram(`
namespace DemoService;
/**
* Type to represent a date
*/
scalar MyDate extends utcDateTime;
`);

const [namespace] = program.resolveTypeReference("DemoService");
const scalar = Array.from((namespace as Namespace).scalars.values())[0];

expect(
<Output program={program} externals={getExternals()}>
<SourceFile path="test.py">
<TypeAliasDeclaration type={scalar} />
</SourceFile>
</Output>,
).toRenderTo(`
from datetime import datetime

# Type to represent a date
MyDate: datetime`);
});

it("can override JSDoc", async () => {
const program = await getProgram(`
namespace DemoService;
/**
* Type to represent a date
*/
scalar MyDate extends utcDateTime;
`);

const [namespace] = program.resolveTypeReference("DemoService");
const scalar = Array.from((namespace as Namespace).scalars.values())[0];

expect(
<Output program={program} externals={getExternals()}>
<SourceFile path="test.py">
<TypeAliasDeclaration doc={"Overridden Doc"} type={scalar} />
</SourceFile>
</Output>,
).toRenderTo(`
from datetime import datetime

# Overridden Doc
MyDate: datetime`);
});

it("creates a type alias declaration for a utcDateTime with unixTimeStamp encoding", async () => {
const program = await getProgram(`
namespace DemoService;
@encode("unixTimestamp", int32)
scalar MyDate extends utcDateTime;
`);

const [namespace] = program.resolveTypeReference("DemoService");
const scalar = Array.from((namespace as Namespace).scalars.values())[0];

expect(
<Output program={program} externals={getExternals()}>
<SourceFile path="test.py">
<TypeAliasDeclaration type={scalar} />
</SourceFile>
</Output>,
).toRenderTo(`
from datetime import datetime

MyDate: datetime`);
});

it("creates a type alias declaration for a utcDateTime with rfc7231 encoding", async () => {
const program = await getProgram(`
namespace DemoService;
@encode("rfc7231")
scalar MyDate extends utcDateTime;
`);

const [namespace] = program.resolveTypeReference("DemoService");
const scalar = Array.from((namespace as Namespace).scalars.values())[0];

expect(
<Output program={program} externals={getExternals()}>
<SourceFile path="test.py">
<TypeAliasDeclaration type={scalar} />
</SourceFile>
</Output>,
).toRenderTo(`
from datetime import datetime

MyDate: datetime`);
});

it("creates a type alias declaration for a utcDateTime with rfc3339 encoding", async () => {
const program = await getProgram(`
namespace DemoService;
@encode("rfc3339")
scalar MyDate extends utcDateTime;
`);

const [namespace] = program.resolveTypeReference("DemoService");
const scalar = Array.from((namespace as Namespace).scalars.values())[0];

expect(
<Output program={program} externals={getExternals()}>
<SourceFile path="test.py">
<TypeAliasDeclaration type={scalar} />
</SourceFile>
</Output>,
).toRenderTo(`
from datetime import datetime

MyDate: datetime`);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as py from "@alloy-js/python";
import type { Type } from "@typespec/compiler";
import { useTsp } from "../../../core/context/tsp-context.js";
import { reportDiagnostic } from "../../../lib.js";
import { declarationRefkeys } from "../../utils/refkey.js";
import { TypeExpression } from "../type-expression/type-expression.jsx";

export interface TypedAliasDeclarationProps extends Omit<py.BaseDeclarationProps, "name"> {
type: Type;
name?: string;
}

/**
* Create a Python type alias declaration. Pass the `type` prop to emit the
* type alias as the provided TypeSpec type.
*/
export function TypeAliasDeclaration(props: TypedAliasDeclarationProps) {
const { $ } = useTsp();

const originalName =
props.name ??
("name" in props.type && typeof props.type.name === "string" ? props.type.name : "");

if (!originalName || originalName === "") {
reportDiagnostic($.program, { code: "type-declaration-missing-name", target: props.type });
}

const doc = props.doc ?? $.type.getDoc(props.type);
const refkeys = declarationRefkeys(props.refkey, props.type);

const name = py.usePythonNamePolicy().getName(originalName, "variable");
// TODO: See how we will handle this kind of scenario:
// type Foo {
// bar(id: String): BarResponse
//
// Bar = Callable[[string], BarResponse]
// class Foo:
// bar: Bar
//
// Maybe this won't done by this emitter, but we might want that eventually to be done by some emitter.
//
return (
// TODO: See if there's a need to make py.VariableDeclaration consider props.children
// (it doesn't at this moment, and there isn't a scenario where we need it)
<py.VariableDeclaration
doc={doc}
name={name}
refkey={refkeys}
omitNone={true}
type={<TypeExpression type={props.type} noReference />}
>
{props.children}
</py.VariableDeclaration>
);
}
Loading