Skip to content

Commit e84219f

Browse files
Execute changes in the Python implementation (alloy-framework#302)
## Create `SingleTypeExpression` Create `TypeReference ` to handle some edge cases on typing (involving typeArgs) to, in addition to the already present `UnionTypeExpression`, more properly represent the typing in Python. ## Refactor `FunctionDeclaration` Refactor FunctionDeclaration to more properly represent each scenario: - FunctionDeclaration: Standalone function. Has the functionType parameter. - MethodDeclaration: Normal method - ClassMethodDeclaration: Class method, prepended with @classmethod - StaticMethodDeclaration: Static method, prepended with @staticmethod - DunderMethodDeclaration: Any dunder method. Any formatting to the name that would normally happen will be skipped - ConstructorDeclaration: Special component for the __new__ dunder method, which has cls as its first argument, but isn't decorated with @classmethod - PropertyDeclaration: Python properties, with setter and deleter support, prepended with @Property Also: - Added abc module to be able to import @staticmethod - Modified createPythonSymbol so it supports a reuseExisting, which looks for existing symbol instead of creating a new one. Intended to handle the @Property special cases ## Split Enum implementations Split Enum implementations into FunctionalEnumDeclaration and ClassEnumDeclaration. ## Split docs components into separate components Make docs components more granular by splitting them into: - ClassDoc - FunctionDoc - MethodDoc - ModuleDoc - PropertyDoc - GeneratorDoc - ExceptionDoc - AttributeDoc These are the only components that are meant to be used directly, and are the only ones that are exported. Also added the equivalent GoogleStyle-prefixed classes, bringing the Google-style implementation of these. --------- Co-authored-by: Steve Rice <srice@pinterest.com>
1 parent 42c9afb commit e84219f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4059
-783
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@alloy-js/python"
5+
---
6+
7+
Create SingleTypeExpression, refactor FunctionDeclaration, split Enum implementations and split docs components into separate components

packages/python/src/builtins/python.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { createModule } from "../create-module.js";
44
// eslint-disable-next-line @typescript-eslint/no-unused-vars
55
type dummy = SymbolCreator;
66

7+
export const abcModule = createModule({
8+
name: "abc",
9+
descriptor: {
10+
".": ["abstractmethod"],
11+
},
12+
});
13+
714
export const enumModule = createModule({
815
name: "enum",
916
descriptor: {

packages/python/src/components/CallSignature.tsx

Lines changed: 17 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,19 @@ import {
55
For,
66
Show,
77
SymbolSlot,
8-
useContext,
98
} from "@alloy-js/core";
10-
import { ParameterDescriptor } from "../parameter-descriptor.js";
9+
import {
10+
isParameterDescriptor,
11+
ParameterDescriptor,
12+
} from "../parameter-descriptor.js";
1113
import { createPythonSymbol } from "../symbol-creation.js";
1214
import { PythonOutputSymbol } from "../symbols/index.js";
1315
import { Atom } from "./Atom.jsx";
14-
import { PythonSourceFileContext } from "./SourceFile.jsx";
1516

1617
export interface CallSignatureParametersProps {
17-
readonly parameters?: ParameterDescriptor[] | string[];
18+
readonly parameters?: (ParameterDescriptor | string)[];
1819
readonly args?: boolean;
1920
readonly kwargs?: boolean;
20-
readonly instanceFunction?: boolean;
21-
readonly classFunction?: boolean;
2221
}
2322

2423
/**
@@ -35,33 +34,10 @@ export interface CallSignatureParametersProps {
3534
* ```
3635
*/
3736
export function CallSignatureParameters(props: CallSignatureParametersProps) {
38-
// Validate that only one of instanceFunction or classFunction is true
39-
if (props.instanceFunction && props.classFunction) {
40-
throw new Error("Cannot be both an instance function and a class function");
41-
}
42-
43-
const sfContext = useContext(PythonSourceFileContext);
44-
const module = sfContext?.module;
4537
const parameters = normalizeAndDeclareParameters(props.parameters ?? []);
4638

4739
const parameterList = computed(() => {
4840
const params = [];
49-
50-
// Add self/cls parameter if instance or class function
51-
if (props.instanceFunction) {
52-
params.push(
53-
parameter({
54-
symbol: createPythonSymbol("self", { module: module }),
55-
}),
56-
);
57-
} else if (props.classFunction) {
58-
params.push(
59-
parameter({
60-
symbol: createPythonSymbol("cls", { module: module }),
61-
}),
62-
);
63-
}
64-
6541
// Add regular parameters
6642
parameters.forEach((param) => {
6743
params.push(parameter(param));
@@ -95,16 +71,7 @@ function parameter(param: DeclaredParameterDescriptor) {
9571
<Show when={!!param.type}>
9672
: <TypeSlot>{param.type}</TypeSlot>
9773
</Show>
98-
<Show when={!!param.optional}>
99-
<Show when={!param.type}>=</Show>
100-
<Show when={!!param.type}> = </Show>
101-
<>
102-
{param.default ?
103-
<Atom jsValue={param.default} />
104-
: "None"}
105-
</>
106-
</Show>
107-
<Show when={!param.optional && param.default !== undefined}>
74+
<Show when={param.default !== undefined}>
10875
<Show when={!param.type}>=</Show>
10976
<Show when={!!param.type}> = </Show>
11077
<>
@@ -122,19 +89,10 @@ interface DeclaredParameterDescriptor
12289
}
12390

12491
function normalizeAndDeclareParameters(
125-
parameters: ParameterDescriptor[] | string[],
92+
parameters: (ParameterDescriptor | string)[],
12693
): DeclaredParameterDescriptor[] {
127-
if (parameters.length === 0) {
128-
return [];
129-
}
130-
if (typeof parameters[0] === "string") {
131-
return (parameters as string[]).map((paramName) => {
132-
const symbol = createPythonSymbol(paramName, {}, "parameter");
133-
134-
return { refkeys: symbol.refkeys, symbol };
135-
});
136-
} else {
137-
return (parameters as ParameterDescriptor[]).map((param) => {
94+
return parameters.map((param) => {
95+
if (isParameterDescriptor(param)) {
13896
const TypeSlot = createSymbolSlot();
13997

14098
const symbol = createPythonSymbol(
@@ -151,17 +109,19 @@ function normalizeAndDeclareParameters(
151109
symbol,
152110
TypeSlot,
153111
};
154-
});
155-
}
112+
} else {
113+
const symbol = createPythonSymbol(param, {}, "parameter");
114+
return { refkeys: symbol.refkeys, symbol };
115+
}
116+
});
156117
}
157118

158119
export interface CallSignatureProps {
159120
/**
160-
* The parameters to the call signature. Can be an array of strings for parameters
161-
* which don't have a type or a default value. Otherwise, it's an array of
162-
* {@link ParameterDescriptor}s.
121+
* The parameters to the call signature. Can be an array of strings (for parameters
122+
* which don't have a type or a default value) or {@link ParameterDescriptor}s.
163123
*/
164-
parameters?: ParameterDescriptor[] | string[];
124+
parameters?: (ParameterDescriptor | string)[];
165125

166126
/**
167127
* The type parameters of the call signature, e.g. for a generic function.
@@ -179,16 +139,6 @@ export interface CallSignatureProps {
179139
*/
180140
kwargs?: boolean;
181141

182-
/**
183-
* Indicates that this is an instance function.
184-
*/
185-
instanceFunction?: boolean; // true if this is an instance function
186-
187-
/**
188-
* Indicates that this is a class function.
189-
*/
190-
classFunction?: boolean; // true if this is a class function
191-
192142
/**
193143
* The return type of the function.
194144
*/
@@ -221,8 +171,6 @@ export function CallSignature(props: CallSignatureProps) {
221171
parameters={props.parameters}
222172
args={props.args}
223173
kwargs={props.kwargs}
224-
instanceFunction={props.instanceFunction}
225-
classFunction={props.classFunction}
226174
/>
227175
);
228176
const typeParams =
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { createMethodSymbol } from "../symbols/factories.js";
2+
import type { CommonFunctionProps } from "./FunctionBase.js";
3+
import { MethodDeclarationBase } from "./MethodBase.js";
4+
5+
/**
6+
* A Python class method declaration component.
7+
*
8+
* @example
9+
* ```tsx
10+
* <py.ClassMethodDeclaration name="create" parameters={[{ name: "value", type: "str" }]}>
11+
* return cls(value)
12+
* </py.ClassMethodDeclaration>
13+
* ```
14+
* Generates:
15+
* ```python
16+
* @classmethod
17+
* def create(cls, value: str) -> None:
18+
* return cls(value)
19+
* ```
20+
*/
21+
export interface ClassMethodDeclarationProps extends CommonFunctionProps {
22+
abstract?: boolean;
23+
}
24+
25+
export function ClassMethodDeclaration(props: ClassMethodDeclarationProps) {
26+
const sym = createMethodSymbol(props.name, { refkeys: props.refkey });
27+
return (
28+
<>
29+
{"@classmethod"}
30+
<hbr />
31+
<MethodDeclarationBase functionType="class" {...props} sym={sym} />
32+
</>
33+
);
34+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { namekey } from "@alloy-js/core";
2+
import { createMethodSymbol } from "../symbols/factories.js";
3+
import type { CommonFunctionProps } from "./FunctionBase.js";
4+
import { MethodDeclarationBase } from "./MethodBase.js";
5+
6+
/**
7+
* A Python constructor declaration for `__new__`.
8+
*
9+
* @example
10+
* ```tsx
11+
* <py.ConstructorDeclaration parameters={[{ name: "value", type: "int" }]}>
12+
* pass
13+
* </py.ConstructorDeclaration>
14+
* ```
15+
* Generates:
16+
* ```python
17+
* def __new__(cls, value: int):
18+
* pass
19+
* ```
20+
*/
21+
export interface ConstructorDeclarationProps
22+
extends Omit<CommonFunctionProps, "name"> {
23+
abstract?: boolean;
24+
}
25+
26+
export function ConstructorDeclaration(props: ConstructorDeclarationProps) {
27+
const name = namekey("__new__", { ignoreNamePolicy: true });
28+
const sym = createMethodSymbol(name, { refkeys: props.refkey });
29+
return (
30+
<MethodDeclarationBase
31+
{...props}
32+
name={name}
33+
functionType="class"
34+
sym={sym}
35+
/>
36+
);
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { isNamekey, namekey } from "@alloy-js/core";
2+
import type { CommonFunctionProps } from "./FunctionBase.js";
3+
import { MethodDeclaration } from "./MethodDeclaration.js";
4+
5+
/**
6+
* A Python dunder method declaration.
7+
*
8+
* @example
9+
* ```tsx
10+
* <py.DunderMethodDeclaration name="__repr__" returnType="str">
11+
* return f"<MyType>"
12+
* </py.DunderMethodDeclaration>
13+
* ```
14+
* Generates:
15+
* ```python
16+
* def __repr__(self) -> str:
17+
* return f"<MyType>"
18+
* ```
19+
*/
20+
export interface DunderMethodDeclarationProps extends CommonFunctionProps {
21+
abstract?: boolean;
22+
}
23+
24+
export function DunderMethodDeclaration(props: DunderMethodDeclarationProps) {
25+
const finalName =
26+
isNamekey(props.name) ?
27+
props.name
28+
: namekey(props.name as string, { ignoreNamePolicy: true });
29+
return <MethodDeclaration {...props} name={finalName} />;
30+
}

packages/python/src/components/EnumDeclaration.tsx

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import { enumModule } from "../builtins/python.js";
88
import { createPythonSymbol } from "../symbol-creation.js";
99
import { BaseDeclarationProps } from "./Declaration.js";
1010
import { EnumMember, EnumMemberProps } from "./EnumMember.js";
11-
import { SimpleCommentBlock } from "./index.js";
1211
import { MemberScope } from "./MemberScope.jsx";
1312
import { PythonBlock } from "./PythonBlock.jsx";
1413

15-
export interface EnumProps extends BaseDeclarationProps {
14+
export interface EnumPropsBase extends BaseDeclarationProps {
1615
/**
1716
* The base type of the enum. One of: 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag'.
1817
* Defaults to 'Enum'.
@@ -22,47 +21,13 @@ export interface EnumProps extends BaseDeclarationProps {
2221
* Members of the enum as an array of objects.
2322
*/
2423
members?: Array<EnumMemberProps>;
25-
/**
26-
* The enum style: 'classic' (default), 'auto', or 'functional'.
27-
*/
28-
style?: "classic" | "auto" | "functional";
2924
/**
3025
* Optional docstring for the enum.
3126
*/
3227
doc?: Children;
3328
}
3429

35-
/**
36-
* A Python enum declaration, following https://docs.python.org/3.11/library/enum.html.
37-
*
38-
* @example
39-
* ```tsx
40-
* <EnumDeclaration name="Direction" style="functional">
41-
* members={[
42-
* { name: "NORTH" },
43-
* { name: "SOUTH" },
44-
* { name: "EAST" },
45-
* { name: "WEST" },
46-
* ]}
47-
* />
48-
* ```
49-
* This will generate:
50-
* ```python
51-
* from enum import Enum
52-
* class Direction(Enum):
53-
* NORTH = "NORTH"
54-
* SOUTH = "SOUTH"
55-
* EAST = "EAST"
56-
* WEST = "WEST"
57-
* ```
58-
*/
59-
export function EnumDeclaration(props: EnumProps) {
60-
// Handle enum styles
61-
if (props.style === "functional") {
62-
return <FunctionalEnumDeclaration {...props} />;
63-
}
64-
return <ClassEnumDeclaration {...props} />;
65-
}
30+
export interface FunctionalEnumProps extends EnumPropsBase {}
6631

6732
/**
6833
* Create a Python enum using the functional syntax.
@@ -105,7 +70,7 @@ export function EnumDeclaration(props: EnumProps) {
10570
* Status = Enum('Status', {'PENDING': 1, 'ACTIVE': 2, 'INACTIVE': 3})
10671
* ```
10772
*/
108-
export function FunctionalEnumDeclaration(props: EnumProps) {
73+
export function FunctionalEnumDeclaration(props: FunctionalEnumProps) {
10974
const sym = createPythonSymbol(
11075
props.name,
11176
{
@@ -143,6 +108,13 @@ export function FunctionalEnumDeclaration(props: EnumProps) {
143108
);
144109
}
145110

111+
export interface ClassEnumProps extends EnumPropsBase {
112+
/**
113+
* Indicates that the enum members should be auto-generated.
114+
*/
115+
auto?: boolean;
116+
}
117+
146118
/**
147119
* Create a Python enum using the class-based syntax.
148120
*
@@ -212,7 +184,7 @@ export function FunctionalEnumDeclaration(props: EnumProps) {
212184
* BLUE = auto()
213185
* ```
214186
*/
215-
export function ClassEnumDeclaration(props: EnumProps) {
187+
export function ClassEnumDeclaration(props: ClassEnumProps) {
216188
const baseType = props.baseType || "Enum";
217189
const sym = createPythonSymbol(
218190
props.name,
@@ -224,20 +196,20 @@ export function ClassEnumDeclaration(props: EnumProps) {
224196
let memberList: Array<EnumMemberProps> = (props.members ?? []).map((m) =>
225197
m.value === undefined ? { ...m, auto: false } : m,
226198
);
227-
if (props.style === "auto") {
199+
if (props.auto) {
228200
memberList = memberList.map((m) =>
229201
m.value === undefined ? { name: m.name, auto: true } : m,
230202
);
231203
}
232204
return (
233205
<CoreDeclaration symbol={sym}>
234-
<Show when={Boolean(props.doc)}>
235-
<SimpleCommentBlock children={props.doc} />
236-
<hbr />
237-
</Show>
238206
class {sym.name}({enumModule["."][baseType]})
239207
<MemberScope ownerSymbol={sym}>
240208
<PythonBlock opener=":">
209+
<Show when={Boolean(props.doc)}>
210+
{props.doc}
211+
<hbr />
212+
</Show>
241213
<For each={memberList} hardline>
242214
{(member) => (
243215
<EnumMember

0 commit comments

Comments
 (0)