jsii-pacmak
Version:
A code generation framework for jsii backend languages
2,270 lines • 95 kB
JavaScript
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _TypeCheckingHelper_stubs, _a, _TypeCheckingStub_PREFIX, _TypeCheckingStub_arguments, _TypeCheckingStub_hash;
Object.defineProperty(exports, "__esModule", { value: true });
const spec = require("@jsii/spec");
const assert = require("assert");
const codemaker_1 = require("codemaker");
const crypto = require("crypto");
const escapeStringRegexp = require("escape-string-regexp");
const fs = require("fs-extra");
const jsii_rosetta_1 = require("jsii-rosetta");
const path = require("path");
const generator_1 = require("../generator");
const logging_1 = require("../logging");
const markdown_1 = require("../markdown");
const target_1 = require("../target");
const util_1 = require("../util");
const version_1 = require("../version");
const _utils_1 = require("./_utils");
const type_name_1 = require("./python/type-name");
const util_2 = require("./python/util");
const version_utils_1 = require("./version-utils");
const index_1 = require("./index");
// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports
const spdxLicenseList = require('spdx-license-list');
const requirementsFile = path.resolve(__dirname, 'python', 'requirements-dev.txt');
// we use single-quotes for multi-line strings to allow examples within the
// docstrings themselves to include double-quotes (see https://github.com/aws/jsii/issues/2569)
const DOCSTRING_QUOTES = "'''";
const RAW_DOCSTRING_QUOTES = `r${DOCSTRING_QUOTES}`;
class Python extends target_1.Target {
constructor(options) {
super(options);
this.generator = new PythonGenerator(options.rosetta, options);
}
async generateCode(outDir, tarball) {
await super.generateCode(outDir, tarball);
}
async build(sourceDir, outDir) {
// Create a fresh virtual env
const venv = await fs.mkdtemp(path.join(sourceDir, '.env-'));
const venvBin = path.join(venv, process.platform === 'win32' ? 'Scripts' : 'bin');
// On Windows, there is usually no python3.exe (the GitHub action workers will have a python3
// shim, but using this actually results in a WinError with Python 3.7 and 3.8 where venv will
// fail to copy the python binary if it's not invoked as python.exe). More on this particular
// issue can be read here: https://bugs.python.org/issue43749
await (0, util_1.shell)(process.platform === 'win32' ? 'python' : 'python3', [
'-m',
'venv',
'--system-site-packages',
venv,
]);
const env = {
...process.env,
PATH: `${venvBin}:${process.env.PATH}`,
VIRTUAL_ENV: venv,
};
const python = path.join(venvBin, 'python');
// Install the necessary things
await (0, util_1.shell)(python, ['-m', 'pip', 'install', '--no-input', '-r', requirementsFile], {
cwd: sourceDir,
env,
retry: { maxAttempts: 5 },
});
// Actually package up our code, both as a sdist and a wheel for publishing.
await (0, util_1.shell)(python, ['setup.py', 'sdist', '--dist-dir', outDir], {
cwd: sourceDir,
env,
});
await (0, util_1.shell)(python, ['-m', 'pip', 'wheel', '--no-deps', '--wheel-dir', outDir, sourceDir], {
cwd: sourceDir,
env,
retry: { maxAttempts: 5 },
});
await (0, util_1.shell)(python, ['-m', 'twine', 'check', path.join(outDir, '*')], {
cwd: sourceDir,
env,
});
}
}
exports.default = Python;
class TypeCheckingHelper {
constructor() {
_TypeCheckingHelper_stubs.set(this, new Array());
}
getTypeHints(fqn, args) {
const stub = new TypeCheckingStub(fqn, args);
__classPrivateFieldGet(this, _TypeCheckingHelper_stubs, "f").push(stub);
return `typing.get_type_hints(${stub.name})`;
}
/** Emits instructions that create the annotations data... */
flushStubs(code) {
for (const stub of __classPrivateFieldGet(this, _TypeCheckingHelper_stubs, "f")) {
stub.emit(code);
}
// Reset the stubs list
__classPrivateFieldSet(this, _TypeCheckingHelper_stubs, [], "f");
}
}
_TypeCheckingHelper_stubs = new WeakMap();
class TypeCheckingStub {
constructor(fqn, args) {
_TypeCheckingStub_arguments.set(this, void 0);
_TypeCheckingStub_hash.set(this, void 0);
// Removing the quoted type names -- this will be emitted at the very end of the module.
__classPrivateFieldSet(this, _TypeCheckingStub_arguments, args.map((arg) => arg.replace(/"/g, '')), "f");
__classPrivateFieldSet(this, _TypeCheckingStub_hash, crypto
.createHash('sha256')
.update(__classPrivateFieldGet(TypeCheckingStub, _a, "f", _TypeCheckingStub_PREFIX))
.update(fqn)
.digest('hex'), "f");
}
get name() {
return `${__classPrivateFieldGet(TypeCheckingStub, _a, "f", _TypeCheckingStub_PREFIX)}${__classPrivateFieldGet(this, _TypeCheckingStub_hash, "f")}`;
}
emit(code) {
code.line();
openSignature(code, 'def', this.name, __classPrivateFieldGet(this, _TypeCheckingStub_arguments, "f"), 'None');
code.line(`"""Type checking stubs"""`);
code.line('pass');
code.closeBlock();
}
}
_a = TypeCheckingStub, _TypeCheckingStub_arguments = new WeakMap(), _TypeCheckingStub_hash = new WeakMap();
_TypeCheckingStub_PREFIX = { value: '_typecheckingstub__' };
const pythonModuleNameToFilename = (name) => {
return path.join(...name.split('.'));
};
const toPythonMethodName = (name, protectedItem = false) => {
let value = (0, util_2.toPythonIdentifier)((0, codemaker_1.toSnakeCase)(name));
if (protectedItem) {
value = `_${value}`;
}
return value;
};
const toPythonPropertyName = (name, constant = false, protectedItem = false) => {
let value = (0, util_2.toPythonIdentifier)((0, codemaker_1.toSnakeCase)(name));
if (constant) {
value = value.toUpperCase();
}
if (protectedItem) {
value = `_${value}`;
}
return value;
};
/**
* Converts a given signature's parameter name to what should be emitted in Python. It slugifies the
* positional parameter names that collide with a lifted prop by appending trailing `_`. There is no
* risk of conflicting with an other positional parameter that ends with a `_` character because
* this is prohibited by the `jsii` compiler (parameter names MUST be camelCase, and only a single
* `_` is permitted when it is on **leading** position)
*
* @param name the name of the parameter that needs conversion.
* @param liftedParamNames the list of "lifted" keyword parameters in this signature. This must be
* omitted when generating a name for a parameter that **is** lifted.
*/
function toPythonParameterName(name, liftedParamNames = new Set()) {
let result = (0, util_2.toPythonIdentifier)((0, codemaker_1.toSnakeCase)(name));
while (liftedParamNames.has(result)) {
result += '_';
}
return result;
}
const setDifference = (setA, setB) => {
const result = new Set();
for (const item of setA) {
if (!setB.has(item)) {
result.add(item);
}
}
return result;
};
/**
* Prepare python members for emission.
*
* If there are multiple members of the same name, they will all map to the same python
* name, so we will filter all deprecated members and expect that there will be only one
* left.
*
* Returns the members in a sorted list.
*/
function prepareMembers(members, resolver) {
// create a map from python name to list of members
const map = {};
for (const m of members) {
let list = map[m.pythonName];
if (!list) {
list = map[m.pythonName] = [];
}
list.push(m);
}
// now return all the members
const ret = new Array();
for (const [name, list] of Object.entries(map)) {
let member;
if (list.length === 1) {
// if we have a single member for this normalized name, then use it
member = list[0];
}
else {
// we found more than one member with the same python name, filter all
// deprecated versions and check that we are left with exactly one.
// otherwise, they will overwrite each other
// see https://github.com/aws/jsii/issues/2508
const nonDeprecated = list.filter((x) => !isDeprecated(x));
if (nonDeprecated.length > 1) {
throw new Error(`Multiple non-deprecated members which map to the Python name "${name}"`);
}
if (nonDeprecated.length === 0) {
throw new Error(`Multiple members which map to the Python name "${name}", but all of them are deprecated`);
}
member = nonDeprecated[0];
}
ret.push(member);
}
return sortMembers(ret, resolver);
}
const sortMembers = (members, resolver) => {
let sortable = new Array();
const sorted = new Array();
const seen = new Set();
// The first thing we want to do, is push any item which is not sortable to the very
// front of the list. This will be things like methods, properties, etc.
for (const member of members) {
if (!isSortableType(member)) {
sorted.push(member);
seen.add(member);
}
else {
sortable.push({ member, dependsOn: new Set(member.dependsOn(resolver)) });
}
}
// Now that we've pulled out everything that couldn't possibly have dependencies,
// we will go through the remaining items, and pull off any items which have no
// dependencies that we haven't already sorted.
while (sortable.length > 0) {
for (const { member, dependsOn } of sortable) {
const diff = setDifference(dependsOn, seen);
if ([...diff].find((dep) => !(dep instanceof PythonModule)) == null) {
sorted.push(member);
seen.add(member);
}
}
const leftover = sortable.filter(({ member }) => !seen.has(member));
if (leftover.length === sortable.length) {
throw new Error(`Could not sort members (circular dependency?). Leftover: ${leftover
.map((lo) => lo.member.pythonName)
.join(', ')}`);
}
else {
sortable = leftover;
}
}
return sorted;
};
function isSortableType(arg) {
return arg.dependsOn !== undefined;
}
class BasePythonClassType {
constructor(generator, pythonName, spec, fqn, opts, docs) {
this.generator = generator;
this.pythonName = pythonName;
this.spec = spec;
this.fqn = fqn;
this.docs = docs;
this.separateMembers = true;
const { bases = [] } = opts;
this.bases = bases;
this.members = [];
}
dependsOn(resolver) {
const dependencies = new Array();
const parent = resolver.getParent(this.fqn);
// We need to return any bases that are in the same module at the same level of
// nesting.
const seen = new Set();
for (const base of this.bases) {
if (spec.isNamedTypeReference(base)) {
if (resolver.isInModule(base)) {
// Given a base, we need to locate the base's parent that is the same as
// our parent, because we only care about dependencies that are at the
// same level of our own.
// TODO: We might need to recurse into our members to also find their
// dependencies.
let baseItem = resolver.getType(base);
let baseParent = resolver.getParent(base);
while (baseParent !== parent) {
baseItem = baseParent;
baseParent = resolver.getParent(baseItem.fqn);
}
if (!seen.has(baseItem.fqn)) {
dependencies.push(baseItem);
seen.add(baseItem.fqn);
}
}
}
}
return dependencies;
}
requiredImports(context) {
return (0, type_name_1.mergePythonImports)(...this.bases.map((base) => (0, type_name_1.toTypeName)(base).requiredImports(context)), ...this.members.map((mem) => mem.requiredImports(context)));
}
addMember(member) {
this.members.push(member);
}
get apiLocation() {
if (!this.fqn) {
throw new Error(`Cannot make apiLocation for ${this.pythonName}, does not have FQN`);
}
return { api: 'type', fqn: this.fqn };
}
emit(code, context) {
context = nestedContext(context, this.fqn);
const classParams = this.getClassParams(context);
openSignature(code, 'class', this.pythonName, classParams);
this.generator.emitDocString(code, this.apiLocation, this.docs, {
documentableItem: `class-${this.pythonName}`,
trailingNewLine: true,
});
if (this.members.length > 0) {
const resolver = this.boundResolver(context.resolver);
let shouldSeparate = false;
for (const member of prepareMembers(this.members, resolver)) {
if (shouldSeparate) {
code.line();
}
shouldSeparate = this.separateMembers;
member.emit(code, { ...context, resolver });
}
}
else {
code.line('pass');
}
code.closeBlock();
if (this.fqn != null) {
context.emittedTypes.add(this.fqn);
}
}
boundResolver(resolver) {
if (this.fqn == null) {
return resolver;
}
return resolver.bind(this.fqn);
}
}
class BaseMethod {
constructor(generator, pythonName, jsName, parameters, returns, docs, isStatic, pythonParent, opts) {
this.generator = generator;
this.pythonName = pythonName;
this.jsName = jsName;
this.parameters = parameters;
this.returns = returns;
this.docs = docs;
this.isStatic = isStatic;
this.pythonParent = pythonParent;
this.classAsFirstParameter = false;
this.returnFromJSIIMethod = true;
this.shouldEmitBody = true;
this.abstract = !!opts.abstract;
this.liftedProp = opts.liftedProp;
this.parent = opts.parent;
}
get apiLocation() {
return {
api: 'member',
fqn: this.parent.fqn,
memberName: this.jsName ?? '',
};
}
requiredImports(context) {
return (0, type_name_1.mergePythonImports)((0, type_name_1.toTypeName)(this.returns).requiredImports(context), ...this.parameters.map((param) => (0, type_name_1.toTypeName)(param).requiredImports(context)), ...liftedProperties(this.liftedProp));
function* liftedProperties(struct) {
if (struct == null) {
return;
}
for (const prop of struct.properties ?? []) {
yield (0, type_name_1.toTypeName)(prop.type).requiredImports(context);
}
for (const base of struct.interfaces ?? []) {
const iface = context.resolver.dereference(base);
for (const imports of liftedProperties(iface)) {
yield imports;
}
}
}
}
emit(code, context, opts) {
const { renderAbstract = true, forceEmitBody = false } = opts ?? {};
const returnType = (0, type_name_1.toTypeName)(this.returns).pythonType(context);
// We cannot (currently?) blindly use the names given to us by the JSII for
// initializers, because our keyword lifting will allow two names to clash.
// This can hopefully be removed once we get https://github.com/aws/jsii/issues/288
// resolved, so build up a list of all of the prop names so we can check against
// them later.
const liftedPropNames = new Set();
if (this.liftedProp?.properties != null) {
for (const prop of this.liftedProp.properties) {
liftedPropNames.add(toPythonParameterName(prop.name));
}
}
// We need to turn a list of JSII parameters, into Python style arguments with
// gradual typing, so we'll have to iterate over the list of parameters, and
// build the list, converting as we go.
const pythonParams = [];
for (const param of this.parameters) {
// We cannot (currently?) blindly use the names given to us by the JSII for
// initializers, because our keyword lifting will allow two names to clash.
// This can hopefully be removed once we get https://github.com/aws/jsii/issues/288
// resolved.
const paramName = toPythonParameterName(param.name, liftedPropNames);
const paramType = (0, type_name_1.toTypeName)(param).pythonType({
...context,
parameterType: true,
});
const paramDefault = param.optional ? ' = None' : '';
pythonParams.push(`${paramName}: ${paramType}${paramDefault}`);
}
const documentableArgs = this.parameters
.map((p) => ({
name: p.name,
docs: p.docs,
definingType: this.parent,
}))
// If there's liftedProps, the last argument is the struct and it won't be _actually_ emitted.
.filter((_, index) => this.liftedProp != null ? index < this.parameters.length - 1 : true)
.map((param) => ({
...param,
name: toPythonParameterName(param.name, liftedPropNames),
}));
// If we have a lifted parameter, then we'll drop the last argument to our params
// and then we'll lift all of the params of the lifted type as keyword arguments
// to the function.
if (this.liftedProp !== undefined) {
// Remove our last item.
pythonParams.pop();
const liftedProperties = this.getLiftedProperties(context.resolver);
if (liftedProperties.length >= 1) {
// All of these parameters are keyword only arguments, so we'll mark them
// as such.
pythonParams.push('*');
// Iterate over all of our props, and reflect them into our params.
for (const prop of liftedProperties) {
const paramName = toPythonParameterName(prop.prop.name);
const paramType = (0, type_name_1.toTypeName)(prop.prop).pythonType({
...context,
parameterType: true,
typeAnnotation: true,
});
const paramDefault = prop.prop.optional ? ' = None' : '';
pythonParams.push(`${paramName}: ${paramType}${paramDefault}`);
}
}
// Document them as keyword arguments
documentableArgs.push(...liftedProperties.map((p) => ({
name: p.prop.name,
docs: p.prop.docs,
definingType: p.definingType,
})));
}
else if (this.parameters.length >= 1 &&
this.parameters[this.parameters.length - 1].variadic) {
// Another situation we could be in, is that instead of having a plain parameter
// we have a variadic parameter where we need to expand the last parameter as a
// *args.
pythonParams.pop();
const lastParameter = this.parameters.slice(-1)[0];
const paramName = toPythonParameterName(lastParameter.name);
const paramType = (0, type_name_1.toTypeName)(lastParameter.type).pythonType(context);
pythonParams.push(`*${paramName}: ${paramType}`);
}
const decorators = new Array();
if (this.jsName !== undefined) {
decorators.push(`@jsii.member(jsii_name="${this.jsName}")`);
}
if (this.decorator !== undefined) {
decorators.push(`@${this.decorator}`);
}
if (renderAbstract && this.abstract) {
decorators.push('@abc.abstractmethod');
}
if (decorators.length > 0) {
for (const decorator of decorators) {
code.line(decorator);
}
}
pythonParams.unshift(slugifyAsNeeded(this.implicitParameter, pythonParams.map((param) => param.split(':')[0].trim())));
openSignature(code, 'def', this.pythonName, pythonParams, returnType);
this.generator.emitDocString(code, this.apiLocation, this.docs, {
arguments: documentableArgs,
documentableItem: `method-${this.pythonName}`,
});
if ((this.shouldEmitBody || forceEmitBody) &&
(!renderAbstract || !this.abstract)) {
emitParameterTypeChecks(code, context, pythonParams.slice(1), `${this.pythonParent.fqn ?? this.pythonParent.pythonName}#${this.pythonName}`);
}
this.emitBody(code, context, renderAbstract, forceEmitBody, liftedPropNames, pythonParams[0], returnType);
code.closeBlock();
}
emitBody(code, context, renderAbstract, forceEmitBody, liftedPropNames, implicitParameter, returnType) {
if ((!this.shouldEmitBody && !forceEmitBody) ||
(renderAbstract && this.abstract)) {
code.line('...');
}
else {
if (this.liftedProp !== undefined) {
this.emitAutoProps(code, context, liftedPropNames);
}
this.emitJsiiMethodCall(code, context, liftedPropNames, implicitParameter, returnType);
}
}
emitAutoProps(code, context, liftedPropNames) {
const lastParameter = this.parameters.slice(-1)[0];
const argName = toPythonParameterName(lastParameter.name, liftedPropNames);
const typeName = (0, type_name_1.toTypeName)(lastParameter.type).pythonType({
...context,
typeAnnotation: false,
});
// We need to build up a list of properties, which are mandatory, these are the
// ones we will specifiy to start with in our dictionary literal.
const liftedProps = this.getLiftedProperties(context.resolver).map((p) => new StructField(this.generator, p.prop, p.definingType));
const assignments = liftedProps
.map((p) => p.pythonName)
.map((v) => `${v}=${v}`);
assignCallResult(code, argName, typeName, assignments);
code.line();
}
emitJsiiMethodCall(code, context, liftedPropNames, implicitParameter, returnType) {
const methodPrefix = this.returnFromJSIIMethod ? 'return ' : '';
const jsiiMethodParams = [];
if (this.classAsFirstParameter) {
if (this.parent === undefined) {
throw new Error('Parent not known.');
}
if (this.isStatic) {
jsiiMethodParams.push((0, type_name_1.toTypeName)(this.parent).pythonType({
...context,
typeAnnotation: false,
}));
}
else {
// Using the dynamic class of `self`.
jsiiMethodParams.push(`${implicitParameter}.__class__`);
}
}
jsiiMethodParams.push(implicitParameter);
if (this.jsName !== undefined) {
jsiiMethodParams.push(`"${this.jsName}"`);
}
// If the last arg is variadic, expand the tuple
const params = [];
for (const param of this.parameters) {
let expr = toPythonParameterName(param.name, liftedPropNames);
if (param.variadic) {
expr = `*${expr}`;
}
params.push(expr);
}
const value = `jsii.${this.jsiiMethod}(${jsiiMethodParams.join(', ')}, [${params.join(', ')}])`;
code.line(`${methodPrefix}${this.returnFromJSIIMethod && returnType
? `typing.cast(${returnType}, ${value})`
: value}`);
}
getLiftedProperties(resolver) {
const liftedProperties = [];
const stack = [this.liftedProp];
const knownIfaces = new Set();
const knownProps = new Set();
for (let current = stack.shift(); current != null; current = stack.shift()) {
knownIfaces.add(current.fqn);
// Add any interfaces that this interface depends on, to the list.
if (current.interfaces !== undefined) {
for (const iface of current.interfaces) {
if (knownIfaces.has(iface)) {
continue;
}
stack.push(resolver.dereference(iface));
knownIfaces.add(iface);
}
}
// Add all of the properties of this interface to our list of properties.
if (current.properties !== undefined) {
for (const prop of current.properties) {
if (knownProps.has(prop.name)) {
continue;
}
liftedProperties.push({ prop, definingType: current });
knownProps.add(prop.name);
}
}
}
return liftedProperties;
}
}
class BaseProperty {
constructor(generator, pythonName, jsName, type, docs, pythonParent, opts) {
this.generator = generator;
this.pythonName = pythonName;
this.jsName = jsName;
this.type = type;
this.docs = docs;
this.pythonParent = pythonParent;
this.shouldEmitBody = true;
const { abstract = false, immutable = false, isStatic = false } = opts;
this.abstract = abstract;
this.immutable = immutable;
this.isStatic = isStatic;
this.parent = opts.parent;
}
get apiLocation() {
return { api: 'member', fqn: this.parent.fqn, memberName: this.jsName };
}
requiredImports(context) {
return (0, type_name_1.toTypeName)(this.type).requiredImports(context);
}
emit(code, context, opts) {
const { renderAbstract = true, forceEmitBody = false } = opts ?? {};
const pythonType = (0, type_name_1.toTypeName)(this.type).pythonType(context);
code.line(`@${this.decorator}`);
code.line(`@jsii.member(jsii_name="${this.jsName}")`);
if (renderAbstract && this.abstract) {
code.line('@abc.abstractmethod');
}
openSignature(code, 'def', this.pythonName, [this.implicitParameter], pythonType,
// PyRight and MyPY both special-case @property, but not custom implementations such as our @classproperty...
// MyPY reports on the re-declaration, but PyRight reports on the initial declaration (duh!)
this.isStatic && !this.immutable
? 'pyright: ignore [reportGeneralTypeIssues]'
: undefined);
this.generator.emitDocString(code, this.apiLocation, this.docs, {
documentableItem: `prop-${this.pythonName}`,
});
// NOTE: No parameters to validate here, this is a getter...
if ((this.shouldEmitBody || forceEmitBody) &&
(!renderAbstract || !this.abstract)) {
code.line(`return typing.cast(${pythonType}, jsii.${this.jsiiGetMethod}(${this.implicitParameter}, "${this.jsName}"))`);
}
else {
code.line('...');
}
code.closeBlock();
if (!this.immutable) {
code.line();
// PyRight and MyPY both special-case @property, but not custom implementations such as our @classproperty...
// MyPY reports on the re-declaration, but PyRight reports on the initial declaration (duh!)
code.line(`@${this.pythonName}.setter${this.isStatic ? ' # type: ignore[no-redef]' : ''}`);
if (renderAbstract && this.abstract) {
code.line('@abc.abstractmethod');
}
openSignature(code, 'def', this.pythonName, [this.implicitParameter, `value: ${pythonType}`], 'None');
if ((this.shouldEmitBody || forceEmitBody) &&
(!renderAbstract || !this.abstract)) {
emitParameterTypeChecks(code, context, [`value: ${pythonType}`], `${this.pythonParent.fqn ?? this.pythonParent.pythonName}#${this.pythonName}`);
code.line(`jsii.${this.jsiiSetMethod}(${this.implicitParameter}, "${this.jsName}", value)`);
}
else {
code.line('...');
}
code.closeBlock();
}
}
}
class Interface extends BasePythonClassType {
emit(code, context) {
context = nestedContext(context, this.fqn);
emitList(code, '@jsii.interface(', [`jsii_type="${this.fqn}"`], ')');
// First we do our normal class logic for emitting our members.
super.emit(code, context);
code.line();
code.line();
// Then, we have to emit a Proxy class which implements our proxy interface.
const proxyBases = this.bases.map((b) =>
// "# type: ignore[misc]" because MyPy cannot check dynamic base classes (naturally)
`jsii.proxy_for(${(0, type_name_1.toTypeName)(b).pythonType({
...context,
typeAnnotation: false,
})}) # type: ignore[misc]`);
openSignature(code, 'class', this.proxyClassName, proxyBases);
this.generator.emitDocString(code, this.apiLocation, this.docs, {
documentableItem: `class-${this.pythonName}`,
trailingNewLine: true,
});
code.line(`__jsii_type__: typing.ClassVar[str] = "${this.fqn}"`);
if (this.members.length > 0) {
for (const member of this.members) {
if (this.separateMembers) {
code.line();
}
member.emit(code, context, { forceEmitBody: true });
}
}
else {
code.line('pass');
}
code.closeBlock();
code.line();
code.line('# Adding a "__jsii_proxy_class__(): typing.Type" function to the interface');
code.line(`typing.cast(typing.Any, ${this.pythonName}).__jsii_proxy_class__ = lambda : ${this.proxyClassName}`);
if (this.fqn != null) {
context.emittedTypes.add(this.fqn);
}
}
getClassParams(context) {
const params = this.bases.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
params.push('typing_extensions.Protocol');
return params;
}
get proxyClassName() {
return `_${this.pythonName}Proxy`;
}
}
class InterfaceMethod extends BaseMethod {
constructor() {
super(...arguments);
this.implicitParameter = 'self';
this.jsiiMethod = 'invoke';
this.shouldEmitBody = false;
}
}
class InterfaceProperty extends BaseProperty {
constructor() {
super(...arguments);
this.decorator = 'builtins.property';
this.implicitParameter = 'self';
this.jsiiGetMethod = 'get';
this.jsiiSetMethod = 'set';
this.shouldEmitBody = false;
}
}
class Struct extends BasePythonClassType {
constructor() {
super(...arguments);
this.directMembers = new Array();
}
addMember(member) {
if (!(member instanceof StructField)) {
throw new Error('Must add StructField to Struct');
}
this.directMembers.push(member);
}
emit(code, context) {
context = nestedContext(context, this.fqn);
const baseInterfaces = this.getClassParams(context);
code.indent('@jsii.data_type(');
code.line(`jsii_type=${JSON.stringify(this.fqn)},`);
emitList(code, 'jsii_struct_bases=[', baseInterfaces, '],');
assignDictionary(code, 'name_mapping', this.propertyMap(), ',', true);
code.unindent(')');
openSignature(code, 'class', this.pythonName, baseInterfaces);
this.emitConstructor(code, context);
for (const member of this.allMembers) {
code.line();
this.emitGetter(member, code, context);
}
this.emitMagicMethods(code);
code.closeBlock();
if (this.fqn != null) {
context.emittedTypes.add(this.fqn);
}
}
requiredImports(context) {
return (0, type_name_1.mergePythonImports)(super.requiredImports(context), ...this.allMembers.map((mem) => mem.requiredImports(context)));
}
getClassParams(context) {
return this.bases.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
}
/**
* Find all fields (inherited as well)
*/
get allMembers() {
return this.thisInterface.allProperties.map((x) => new StructField(this.generator, x.spec, x.definingType.spec));
}
get thisInterface() {
if (this.fqn == null) {
throw new Error('FQN not set');
}
return this.generator.reflectAssembly.system.findInterface(this.fqn);
}
emitConstructor(code, context) {
const members = this.allMembers;
const kwargs = members.map((m) => m.constructorDecl(context));
const implicitParameter = slugifyAsNeeded('self', members.map((m) => m.pythonName));
const constructorArguments = kwargs.length > 0
? [implicitParameter, '*', ...kwargs]
: [implicitParameter];
openSignature(code, 'def', '__init__', constructorArguments, 'None');
this.emitConstructorDocstring(code);
// Re-type struct arguments that were passed as "dict". Do this before validating argument types...
for (const member of members.filter((m) => m.isStruct(this.generator))) {
// Note that "None" is NOT an instance of dict (that's convenient!)
const typeName = (0, type_name_1.toTypeName)(member.type.type).pythonType({
...context,
typeAnnotation: false,
});
code.openBlock(`if isinstance(${member.pythonName}, dict)`);
code.line(`${member.pythonName} = ${typeName}(**${member.pythonName})`);
code.closeBlock();
}
if (kwargs.length > 0) {
emitParameterTypeChecks(code,
// Runtime type check keyword args as this is a struct __init__ function.
{ ...context, runtimeTypeCheckKwargs: true }, ['*', ...kwargs], `${this.fqn ?? this.pythonName}#__init__`);
}
// Required properties, those will always be put into the dict
assignDictionary(code, `${implicitParameter}._values: typing.Dict[builtins.str, typing.Any]`, members
.filter((m) => !m.optional)
.map((member) => `${JSON.stringify(member.pythonName)}: ${member.pythonName}`));
// Optional properties, will only be put into the dict if they're not None
for (const member of members.filter((m) => m.optional)) {
code.openBlock(`if ${member.pythonName} is not None`);
code.line(`${implicitParameter}._values["${member.pythonName}"] = ${member.pythonName}`);
code.closeBlock();
}
code.closeBlock();
}
emitConstructorDocstring(code) {
const args = this.allMembers.map((m) => ({
name: m.pythonName,
docs: m.docs,
definingType: this.spec,
}));
this.generator.emitDocString(code, this.apiLocation, this.docs, {
arguments: args,
documentableItem: `class-${this.pythonName}`,
});
}
emitGetter(member, code, context) {
const pythonType = member.typeAnnotation(context);
code.line('@builtins.property');
openSignature(code, 'def', member.pythonName, ['self'], pythonType);
member.emitDocString(code);
// NOTE: No parameter to validate here, this is a getter.
code.line(`result = self._values.get(${JSON.stringify(member.pythonName)})`);
if (!member.optional) {
// Add an assertion to maye MyPY happy!
code.line(`assert result is not None, "Required property '${member.pythonName}' is missing"`);
}
code.line(`return typing.cast(${pythonType}, result)`);
code.closeBlock();
}
emitMagicMethods(code) {
code.line();
code.openBlock('def __eq__(self, rhs: typing.Any) -> builtins.bool');
code.line('return isinstance(rhs, self.__class__) and rhs._values == self._values');
code.closeBlock();
code.line();
code.openBlock('def __ne__(self, rhs: typing.Any) -> builtins.bool');
code.line('return not (rhs == self)');
code.closeBlock();
code.line();
code.openBlock('def __repr__(self) -> str');
code.indent(`return "${this.pythonName}(%s)" % ", ".join(`);
code.line('k + "=" + repr(v) for k, v in self._values.items()');
code.unindent(')');
code.closeBlock();
}
propertyMap() {
const ret = new Array();
for (const member of this.allMembers) {
ret.push(`${JSON.stringify(member.pythonName)}: ${JSON.stringify(member.jsiiName)}`);
}
return ret;
}
}
class StructField {
constructor(generator, prop, definingType) {
this.generator = generator;
this.prop = prop;
this.definingType = definingType;
this.pythonName = toPythonPropertyName(prop.name);
this.jsiiName = prop.name;
this.type = prop;
this.docs = prop.docs;
}
get apiLocation() {
return {
api: 'member',
fqn: this.definingType.fqn,
memberName: this.jsiiName,
};
}
get optional() {
return !!this.type.optional;
}
requiredImports(context) {
return (0, type_name_1.toTypeName)(this.type).requiredImports(context);
}
isStruct(generator) {
return isStruct(generator.reflectAssembly.system, this.type.type);
}
constructorDecl(context) {
const opt = this.optional ? ' = None' : '';
return `${this.pythonName}: ${this.typeAnnotation({
...context,
parameterType: true,
})}${opt}`;
}
/**
* Return the Python type annotation for this type
*/
typeAnnotation(context) {
return (0, type_name_1.toTypeName)(this.type).pythonType(context);
}
emitDocString(code) {
this.generator.emitDocString(code, this.apiLocation, this.docs, {
documentableItem: `prop-${this.pythonName}`,
});
}
emit(code, context) {
const resolvedType = this.typeAnnotation(context);
code.line(`${this.pythonName}: ${resolvedType}`);
this.emitDocString(code);
}
}
class Class extends BasePythonClassType {
constructor(generator, name, spec, fqn, opts, docs) {
super(generator, name, spec, fqn, opts, docs);
const { abstract = false, interfaces = [], abstractBases = [] } = opts;
this.abstract = abstract;
this.interfaces = interfaces;
this.abstractBases = abstractBases;
}
dependsOn(resolver) {
const dependencies = super.dependsOn(resolver);
const parent = resolver.getParent(this.fqn);
// We need to return any ifaces that are in the same module at the same level of
// nesting.
const seen = new Set();
for (const iface of this.interfaces) {
if (resolver.isInModule(iface)) {
// Given a iface, we need to locate the ifaces's parent that is the same
// as our parent, because we only care about dependencies that are at the
// same level of our own.
// TODO: We might need to recurse into our members to also find their
// dependencies.
let ifaceItem = resolver.getType(iface);
let ifaceParent = resolver.getParent(iface);
while (ifaceParent !== parent) {
ifaceItem = ifaceParent;
ifaceParent = resolver.getParent(ifaceItem.fqn);
}
if (!seen.has(ifaceItem.fqn)) {
dependencies.push(ifaceItem);
seen.add(ifaceItem.fqn);
}
}
}
return dependencies;
}
requiredImports(context) {
return (0, type_name_1.mergePythonImports)(super.requiredImports(context), // Takes care of base & members
...this.interfaces.map((base) => (0, type_name_1.toTypeName)(base).requiredImports(context)));
}
emit(code, context) {
// First we emit our implments decorator
if (this.interfaces.length > 0) {
const interfaces = this.interfaces.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
code.line(`@jsii.implements(${interfaces.join(', ')})`);
}
// Then we do our normal class logic for emitting our members.
super.emit(code, context);
// Then, if our class is Abstract, we have to go through and redo all of
// this logic, except only emiting abstract methods and properties as non
// abstract, and subclassing our initial class.
if (this.abstract) {
context = nestedContext(context, this.fqn);
const proxyBases = [this.pythonName];
for (const base of this.abstractBases) {
// "# type: ignore[misc]" because MyPy cannot check dynamic base classes (naturally)
proxyBases.push(`jsii.proxy_for(${(0, type_name_1.toTypeName)(base).pythonType({
...context,
typeAnnotation: false,
})}) # type: ignore[misc]`);
}
code.line();
code.line();
openSignature(code, 'class', this.proxyClassName, proxyBases);
// Filter our list of members to *only* be abstract members, and not any
// other types.
const abstractMembers = this.members.filter((m) => (m instanceof BaseMethod || m instanceof BaseProperty) && m.abstract);
if (abstractMembers.length > 0) {
let first = true;
for (const member of abstractMembers) {
if (this.separateMembers) {
if (first) {
first = false;
}
else {
code.line();
}
}
member.emit(code, context, { renderAbstract: false });
}
}
else {
code.line('pass');
}
code.closeBlock();
code.line();
code.line('# Adding a "__jsii_proxy_class__(): typing.Type" function to the abstract class');
code.line(`typing.cast(typing.Any, ${this.pythonName}).__jsii_proxy_class__ = lambda : ${this.proxyClassName}`);
}
}
getClassParams(context) {
const params = this.bases.map((b) => (0, type_name_1.toTypeName)(b).pythonType({ ...context, typeAnnotation: false }));
const metaclass = this.abstract ? 'JSIIAbstractClass' : 'JSIIMeta';
params.push(`metaclass=jsii.${metaclass}`);
params.push(`jsii_type="${this.fqn}"`);
return params;
}
get proxyClassName() {
return `_${this.pythonName}Proxy`;
}
}
class StaticMethod extends BaseMethod {
constructor() {
super(...arguments);
this.decorator = 'builtins.classmethod';
this.implicitParameter = 'cls';
this.jsiiMethod = 'sinvoke';
}
}
class Initializer extends BaseMethod {
constructor() {
super(...arguments);
this.implicitParameter = 'self';
this.jsiiMethod = 'create';
this.classAsFirstParameter = true;
this.returnFromJSIIMethod = false;
}
}
class Method extends BaseMethod {
constructor() {
super(...arguments);
this.implicitParameter = 'self';
this.jsiiMethod = 'invoke';
}
}
class AsyncMethod extends BaseMethod {
constructor() {
super(...arguments);
this.implicitParameter = 'self';
this.jsiiMethod = 'ainvoke';
}
}
class StaticProperty extends BaseProperty {
constructor() {
super(...arguments);
this.decorator = 'jsii.python.classproperty';
this.implicitParameter = 'cls';
this.jsiiGetMethod = 'sget';
this.jsiiSetMethod = 'sset';
}
}
class Property extends BaseProperty {
constructor() {
super(...arguments);
this.decorator = 'builtins.property';
this.implicitParameter = 'self';
this.jsiiGetMethod = 'get';
this.jsiiSetMethod = 'set';
}
}
class Enum extends BasePythonClassType {
constructor() {
super(...arguments);
this.separateMembers = false;
}
emit(code, context) {
context = nestedContext(context, this.fqn);
emitList(code, '@jsii.enum(', [`jsii_type="${this.fqn}"`], ')');
return super.emit(code, context);
}
getClassParams(_context) {
return ['enum.Enum'];
}
requiredImports(context) {
return super.requiredImports(context);
}
}
class EnumMember {
constructor(generator, pythonName, value, docs, parent) {
this.generator = generator;
this.pythonName = pythonName;
this.value = value;
this.docs = docs;
this.parent = parent;
this.pythonName = pythonName;
this.value = value;
}
get apiLocation() {
return { api: 'member', fqn: this.parent.fqn, memberName: this.value };
}
dependsOnModules() {
return new Set();
}
emit(code, _context) {
code.line(`${this.pythonName} = "${this.value}"`);
this.generator.emitDocString(code, this.apiLocation, this.docs, {
documentableItem: `enum-${this.pythonName}`,
});
}
requiredImports(_context) {
return {};
}
}
/**
* Python module
*
* Will be called for jsii submodules and namespaces.
*/
class PythonModule {
constructor(pythonName, fqn, opts) {
this.pythonName = pythonName;
this.fqn = fqn;
this.members = new Array();
this.modules = new Array();
this.assembly = opts.assembly;
this.assemblyFilename = opts.assemblyFilename;
this.loadAssembly = !!opts.loadAssembly;
this.moduleDocumentation = opts.moduleDocumentation;
}
addMember(member) {
this.members.push(member);
}
addPythonModule(pyMod) {
assert(!this.loadAssembly, 'PythonModule.addPythonModule CANNOT be called on assembly-loading modules (it would cause a load cycle)!');
assert(pyMod.pythonName.startsWith(`${this.pythonName}.`), `Attempted to register ${pyMod.pythonName} as a child module of ${this.pythonName}, but the names don't match!`);
const [firstLevel, ...rest] = pyMod.pythonName
.substring(this.pythonName.length + 1)
.split('.');
if (rest.length === 0) {
// This is a direct child module...
this.modules.push(pyMod);
}
else {
// This is a nested child module, so we delegate to the directly nested module...
const parent = this.modules.find((m) => m.pythonName === `${this.pythonName}.${firstLevel}`);
if (!parent) {
throw new Error(`Attempted to register ${pyMod.pythonName} within ${this.pythonName}, but ${this.pythonName}.${firstLevel} wasn't registered yet!`);
}
parent.addPythonModule(pyMod);
}
}
requiredImports(context) {
return (0, type_name_1.mergePythonImports)(...this.members.map((mem) => mem.requiredImports(context)));
}
emit(code, context) {
this.emitModuleDocumentation(code);
const resolver = this.fqn
? context.resolver.bind(this.fqn, this.pythonName)
: context.resolver;
context = {
...context,
submodule: this.fqn ?? context.submodule,
resolver,
};
// Before we write anything else, we need to write out our module headers, this
// is where we handle stuff like imports, any required initialization, etc.
// If multiple packages use the same namespace (in Python, a directory) it
// depends on how they are laid out on disk if deep imports of multiple packages
// will succeed. `pip` merges all packages into the same directory, and deep
// imports work automatically. `bazel` puts packages into different directories,
// and `import aws_cdk.subpackage` will fail if `aws_cdk/__init__.py` and
// `aws_cdk/subpackage/__init__.py` are not in the same directory.
//
// We can get around this by using `pkgutil` to extend the search path for the
// current module (`__path__`) with all packages found on `sys.path`.
code.line('from pkgutil import extend_path');
code.line('__path__ = extend_path(__path__, __name__)');
code.line();
code.line('import abc');
code.line('import builtins');
code.line('import datetime');
code.line('import enum');
code.line('import typing');
code.line();
code.line('import jsii');
code.line('import publication');
code.line('import typing_extensions');
code.line();
code.line('from typeguard import check_type');
// Determine if we need to write out the kernel load line.
if (this.loadAssembly) {
this.emitDependencyImports(code);
code.line();
emitList(code, '__jsii_assembly__ = jsii.JSIIAssembly.load(', [
JSON.stringify(this.assembly.name),
JSON.stringify(this.assembly.version),
'__name__[0:-6]',
`${JSON.stringify(this.assemblyFilename)}`,
], ')');
}
else {
// Then we must import the ._jsii subpackage.
code.line();
let distanceFromRoot = 0;
for (let curr = this.fqn; curr !== this.assembly.name; curr = curr.substring(0, curr.lastIndexOf('.'))) {
distanceFromRoot++;
}
code.line(`from ${'.'.repeat(distanceFromRoot + 1)}_jsii import *`);
this.emitRequiredImports(code, context);
}
// Emit all of our members.
for (const member of prepareMembers(this.members, resolver)) {
code.line();
code.line();
member.emit(code, context);
}
// Whatever names we've exported, we'll write out our __all__ that lists them.
//
// __all__ is normally used for when users write `from library import *`, but we also
// use it with the `publication` module to hide everything that's NOT in the list.
//
// Normally adding submodules to `__all__` has the (negative?) side-effect
// that all submodules get loaded when the user does `import *`, but we
// already load submodules anyway so it doesn't make a difference, and in combination
// with the `publication` module NOT having them in this list hides any submodules
// we import as part of typechecking.
const exportedMembers = [
...this.members.map((m) => `"${m.pythonName}"`),
...this.modules
.filter((m) => this.isDirectChild(m))
.map((m) => `"${lastComponent(m.pythonName)}"`),
];
if (this.loadAssembly) {
exportedMembers.push('"__jsii_assembly__"');
}
// Declare the list of "public" members this module exports
if (this.members.length > 0) {
code.line();
}
code.line();
if (exportedMembers.length > 0) {
code.indent('__all__ = [');
for (const member of exportedMembers.sort()) {
// Writing one by line might be _a lot_ of lines, but it'll make reviewing changes to the list easier. Trust me.
code.line(`${member},`);
}
code.unindent(']');
}
else {
code.line('__all__: typing.List[typing.Any] = []');
}
// Next up, we'll use publication to ensure that all of the non-public names
// get hidden from dir(), tab-complete, etc.
code.line();
code.line('publication.publish()');
// Finally, we'll load all registered python modules
if (this.modules.length > 0) {
code.line();
code.line('# Loading modules to ensure their types are registered with the jsii runtime library');
for (const module of this.modules.sort((l, r) => l.pythonName.localeCompare(r.pythonName))) {
// Rather than generating an absolute import like
// "import jsii_calc.submodule" this builds a relative import like
// "from . import submodule". This enables distributing python packages
// and using the generated modules in the same codebase.
const submodule = module.pythonName.substring(this.pythonName.length + 1);
code.line(`from . import ${submodule}`);
}
}
}
/**
* Emit the bin scripts if bin section defined.
*/
emitBinScripts(code) {
const scripts = new Array();
if (this.loadAssembly) {
if (this.assembly.bin != null) {
for (const name of Object.keys(this.assembly.bin)) {
const script_file = path.join('src', pythonModuleNameToFilename(this.pythonName), 'bin', name);
code.openFile(script_file);
code.line('#!/usr/bin/env python');
code.line();
code.line('import jsii');
code.line('import sys');
code.line('import os');
code.line();
code.openBlock('if "JSII_RUNTIME_PACKAGE_CACHE" not in os.environ');
code.line('os.environ["JSII_RUNTIME_PACKAGE_CACHE"] = "disabled"');
code.closeBlock();
code.line();
emitList(code, '__jsii_assembly__ = jsii.JSIIAssembly.load(', [
JSON.stringify(this.assembly.name),
JSON.stringify(this.assembly.version),
JSON.stringify(this.pythonName.replace('._jsii', '')),
`${JSON.stringify(this.assemblyFilename)}`,
], ')');
code.line();
emitList(code, 'exit_code = __jsii_assembly__.invokeBinScript(', [
JSON.stringify(this.assembly.name),
JSON.stringify(name),
'sys.argv[1:]',
], ')');
code.line('exit(exit_code)');
code.closeFile(script_file);
scripts.push(script_file.replace(/\\/g, '/'));
}
}
}
return scripts;
}
isDirectChild(pyMod) {
if (this.pythonName === pyMod.pythonName ||
!pyMod.pythonName.startsWith(`${this.pythonName}.`)) {
return false;
}
// Must include only one more component
return !pyMod.pythonName
.substring(this.pythonName.length + 1)
.includes('.');
}
/**
* Emit the README as module docstring if this is the entry point module (it loads the assembly)
*/
emitModuleDocumentation(code) {
if (this.moduleDocumentation) {
code.line(RAW_DOCSTRING_QUOTES); // raw string so that python does not attempt to interpret invalid escapes that are valid in markdown
code.line(this.moduleDocumentation);
code.line(DOCSTRING_QUOTES);
}
}
emitDependencyImports(code) {
// Collect all the (direct) dependencies' ._jsii packages.
const deps = Object.keys(this.assembly.dependencies ?? {})
.map((dep) => this.assembly.dependencyClosure?.[dep]?.targets?.python?.module ??
(0, util_2.die)(`No Python target was configrued for the dependency "${dep}".`))
.map((mod) => `${mod}._jsii`)
.sort();
// Now actually write the import statements...
if (deps.length > 0) {
code.line();
for (const moduleName of deps) {
code.line(`import ${moduleName}`);
}
}
}
emitRequiredImports(code, context) {
const requiredImports = this.requiredImports(context);
const statements = Object.entries(requiredImports)
.map(([sourcePackage, items]) => toImportStatements(sourcePackage, items))
.reduce((acc, elt) => [...acc, ...elt], new Array())
.sort(importComparator);
if (statements.length > 0) {
code.line();
}
for (const statement of statements) {
statement.emit(code);
}
function toImportStatements(sourcePkg, items) {
const result = new Array();
if (items.has('')) {
result.push({
comparisonBase: `import ${sourcePkg}`,
emit(code) {
code.line(this.comparisonBase);
},
});
}
const pieceMeal = Array.from(items)
.filter((i) => i !== '')
.sort();
if (pieceMeal.length > 0) {
result.push({
comparisonBase: `from ${sourcePkg} import`,
emit: (code) => emitList(code, `from ${sourcePkg} import `, pieceMeal, '', {
ifMulti: ['(', ')'],
}),
});
}
return result;
}
function importComparator(left, right) {
if (left.comparisonBase.startsWith('import') ===
right.comparisonBase.startsWith('import')) {
return left.comparisonBase.localeCompare(right.comparisonBase);
}
// We want "from .foo import (...)" to be *after* "import bar"
return right.comparisonBase.localeCompare(left.comparisonBase);
}
}
}
class Package {
constructor(name, version, metadata) {
this.modules = new Map();
this.data = new Map();
this.name = name;
this.version = version;
this.metadata = metadata;
}
addModule(module) {
this.modules.set(module.pythonName, module);
// This is the module that represents the assembly
if (module.fqn === this.metadata.name) {
this.rootModule = module;
}
}
addData(module, filename, data) {
if (!this.data.has(module.pythonName)) {
this.data.set(module.pythonName, []);
}
this.data.get(module.pythonName).push({ filename, data });
}
write(code, context) {
const modules = [...this.modules.values()].sort((a, b) => a.pythonName.localeCompare(b.pythonName));
const scripts = new Array();
// Iterate over all of our modules, and write them out to disk.
for (const mod of modules) {
const filename = path.join('src', pythonModuleNameToFilename(mod.pythonName), '__init__.py');
code.openFile(filename);
mod.emit(code, context);
context.typeCheckingHelper.flushStubs(code);
code.closeFile(filename);
scripts.push(...mod.emitBinScripts(code));
}
// Handle our package data.
const packageData = {};
for (const [mod, pdata] of this.data) {
for (const data of pdata) {
if (data.data != null) {
const filepath = path.join('src', pythonModuleNameToFilename(mod), data.filename);
code.openFile(filepath);
code.line(data.data);
code.closeFile(filepath);
}
}
packageData[mod] = pdata.map((pd) => pd.filename);
}
// Compute our list of dependencies
const dependencies = [];
for (const [depName, version] of Object.entries(this.metadata.dependencies ?? {})) {
const depInfo = this.metadata.dependencyClosure[depName];
dependencies.push(`${depInfo.targets.python.distName}${(0, version_utils_1.toPythonVersionRange)(version)}`);
}
// Need to always write this file as the build process depends on it.
// Make up some contents if we don't have anything useful to say.
code.openFile('README.md');
code.line(this.rootModule?.moduleDocumentation ??
`${this.name}\n${'='.repeat(this.name.length)}`);
code.closeFile('README.md');
const setupKwargs = {
name: this.name,
version: this.version,
description: this.metadata.description,
license: this.metadata.license,
url: this.metadata.homepage,
long_description_content_type: 'text/markdown',
author: this.metadata.author.name +
(this.metadata.author.email !== undefined
? `<${this.metadata.author.email}>`
: ''),
bdist_wheel: {
universal: true,
},
project_urls: {
Source: this.metadata.repository.url,
},
package_dir: { '': 'src' },
packages: modules.map((m) => m.pythonName),
package_data: packageData,
python_requires: '~=3.8',
install_requires: [
`jsii${(0, version_utils_1.toPythonVersionRange)(`^${version_1.VERSION}`)}`,
'publication>=0.0.3',
'typeguard~=2.13.3',
]
.concat(dependencies)
.sort(),
classifiers: [
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: JavaScript',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Typing :: Typed',
],
scripts,
};
// Packages w/ a deprecated message may have a non-deprecated stability (e.g: when EoL happens
// for a stable package). We pretend it's deprecated for the purpose of trove classifiers when
// this happens.
switch (this.metadata.docs?.deprecated
? spec.Stability.Deprecated
: this.metadata.docs?.stability) {
case spec.Stability.Experimental:
setupKwargs.classifiers.push('Development Status :: 4 - Beta');
break;
case spec.Stability.Stable:
setupKwargs.classifiers.push('Development Status :: 5 - Production/Stable');
break;
case spec.Stability.Deprecated:
setupKwargs.classifiers.push('Development Status :: 7 - Inactive');
break;
default:
// No 'Development Status' trove classifier for you!
}
if (spdxLicenseList[this.metadata.license]?.osiApproved) {
setupKwargs.classifiers.push('License :: OSI Approved');
}
const additionalClassifiers = this.metadata.targets?.python?.classifiers;
if (additionalClassifiers != null) {
if (!Array.isArray(additionalClassifiers)) {
throw new Error(`The "jsii.targets.python.classifiers" value must be an array of strings if provided, but found ${JSON.stringify(additionalClassifiers, null, 2)}`);
}
// We discourage using those since we automatically set a value for them
for (let classifier of additionalClassifiers.sort()) {
if (typeof classifier !== 'string') {
throw new Error(`The "jsii.targets.python.classifiers" value can only contain strings, but found ${JSON.stringify(classifier, null, 2)}`);
}
// We'll split on `::` and re-join later so classifiers are "normalized" to a standard spacing
const parts = classifier.split('::').map((part) => part.trim());
const reservedClassifiers = [
'Development Status',
'License',
'Operating System',
'Typing',
];
if (reservedClassifiers.includes(parts[0])) {
(0, logging_1.warn)(`Classifiers starting with ${reservedClassifiers
.map((x) => `"${x} ::"`)
.join(', ')} are automatically set and should not be manually configured`);
}
classifier = parts.join(' :: ');
if (setupKwargs.classifiers.includes(classifier)) {
continue;
}
setupKwargs.classifiers.push(classifier);
}
}
// We Need a setup.py to make this Package, actually a Package.
code.openFile('setup.py');
code.line('import json');
code.line('import setuptools');
code.line();
code.line('kwargs = json.loads(');
code.line(' """');
code.line(JSON.stringify(setupKwargs, null, 4));
code.line('"""');
code.line(')');
code.line();
code.openBlock('with open("README.md", encoding="utf8") as fp');
code.line('kwargs["long_description"] = fp.read()');
code.closeBlock();
code.line();
code.line();
code.line('setuptools.setup(**kwargs)');
code.closeFile('setup.py');
// Because we're good citizens, we're going to go ahead and support pyproject.toml
// as well.
// TODO: Might be easier to just use a TOML library to write this out.
code.openFile('pyproject.toml');
code.line('[build-system]');
const buildTools = fs
.readFileSync(requirementsFile, { encoding: 'utf-8' })
.split('\n')
.map((line) => /^\s*(.+)\s*#\s*build-system\s*$/.exec(line)?.[1]?.trim())
.reduce((buildTools, entry) => (entry ? [...buildTools, entry] : buildTools), new Array());
code.line(`requires = [${buildTools.map((x) => `"${x}"`).join(', ')}]`);
code.line('build-backend = "setuptools.build_meta"');
code.line();
code.line('[tool.pyright]');
code.line('defineConstant = { DEBUG = true }');
code.line('pythonVersion = "3.8"');
code.line('pythonPlatform = "All"');
code.line('reportSelfClsParameterName = false');
code.closeFile('pyproject.toml');
// We also need to write out a MANIFEST.in to ensure that all of our required
// files are included.
code.openFile('MANIFEST.in');
code.line('include pyproject.toml');
code.closeFile('MANIFEST.in');
}
}
class TypeResolver {
constructor(types, findModule, findType, boundTo, moduleName) {
this.types = types;
this.findModule = findModule;
this.findType = findType;
this.moduleName = moduleName;
this.boundTo = boundTo !== undefined ? this.toPythonFQN(boundTo) : boundTo;
if (this.moduleName !== undefined) {
this.moduleRe = new RegExp(`^(${escapeStringRegexp(this.moduleName)})\\.(.+)$`);
}
if (this.boundTo !== undefined) {
this.boundRe = new RegExp(`^(${escapeStringRegexp(this.boundTo)})\\.(.+)$`);
}
}
bind(fqn, moduleName) {
return new TypeResolver(this.types, this.findModule, this.findType, fqn, moduleName !== undefined
? moduleName.startsWith('.')
? `${this.moduleName}${moduleName}`
: moduleName
: this.moduleName);
}
isInModule(typeRef) {
const pythonType = typeof typeRef !== 'string' ? this.toPythonFQN(typeRef.fqn) : typeRef;
return this.moduleRe.test(pythonType);
}
isInNamespace(typeRef) {
const pythonType = typeof typeRef !== 'string' ? this.toPythonFQN(typeRef.fqn) : typeRef;
return this.boundRe.test(pythonType);
}
getParent(typeRef) {
const fqn = typeof typeRef !== 'string' ? typeRef.fqn : typeRef;
const matches = /^(.+)\.[^.]+$/.exec(fqn);
if (matches == null || !Array.isArray(matches)) {
throw new Error(`Invalid FQN: ${fqn}`);
}
const [, parentFQN] = matches;
const parent = this.types.get(parentFQN);
if (parent === undefined) {
throw new Error(`Could not find parent: ${parentFQN}`);
}
return parent;
}
getDefiningPythonModule(typeRef) {
const fqn = typeof typeRef !== 'string' ? typeRef.fqn : typeRef;
const parent = this.types.get(fqn);
if (parent) {
let mod = parent;
while (!(mod instanceof PythonModule)) {
mod = this.getParent(mod.fqn);
}
return mod.pythonName;
}
const matches = /^([^.]+)\./.exec(fqn);
if (matches == null || !Array.isArray(matches)) {
throw new Error(`Invalid FQN: ${fqn}`);
}
const [, assm] = matches;
return this.findModule(assm).targets.python.module;
}
getType(typeRef) {
const type = this.types.get(typeRef.fqn);
if (type === undefined) {
throw new Error(`Could not locate type: "${typeRef.fqn}"`);
}
return type;
}
dereference(typeRef) {
if (typeof typeRef !== 'string') {
typeRef = typeRef.fqn;
}
return this.findType(typeRef);
}
toPythonFQN(fqn) {
const [assemblyName, ...qualifiedIdentifiers] = fqn.split('.');
const fqnParts = [
this.findModule(assemblyName).targets.python.module,
];
for (const part of qualifiedIdentifiers) {
fqnParts.push((0, util_2.toPythonIdentifier)(part));
}
return fqnParts.join('.');
}
}
class PythonGenerator extends generator_1.Generator {
constructor(rosetta, options) {
super(options);
this.rosetta = rosetta;
this.code.openBlockFormatter = (s) => `${s}:`;
this.code.closeBlockFormatter = (_s) => false;
this.types = new Map();
}
// eslint-disable-next-line complexity
emitDocString(code, apiLocation, docs, options = {}) {
if ((!docs || Object.keys(docs).length === 0) && !options.arguments) {
return;
}
if (!docs) {
docs = {};
}
const lines = new Array();
if (docs.summary) {
lines.push((0, markdown_1.md2rst)((0, _utils_1.renderSummary)(docs)));
brk();
}
else {
lines.push('');
}
function brk() {
if (lines.length > 0 && lines[lines.length - 1].trim() !== '') {
lines.push('');
}
}
function block(heading, content, doBrk = true) {
if (doBrk) {
brk();
}
const contentLines = (0, markdown_1.md2rst)(content).split('\n');
if (contentLines.length <= 1) {
lines.push(`:${heading}: ${contentLines.join('')}`.trim());
}
else {
lines.push(`:${heading}:`);
brk();
for (const line of contentLines) {
lines.push(line.trim());
}
}
if (doBrk) {
brk();
}
}
if (docs.remarks) {
brk();
lines.push(...(0, markdown_1.md2rst)(this.convertMarkdown(docs.remarks ?? '', apiLocation)).split('\n'));
brk();
}
if (options.arguments?.length ?? 0 > 0) {
brk();
for (const param of options.arguments) {
// Add a line for every argument. Even if there is no description, we need
// the docstring so that the Sphinx extension can add the type annotations.
lines.push(`:param ${toPythonParameterName(param.name)}: ${onelineDescription(param.docs)}`);
}
brk();
}
if (docs.default) {
block('default', docs.default);
}
if (docs.returns) {
block('return', docs.returns);
}
if (docs.deprecated) {
block('deprecated', docs.deprecated);
}
if (docs.see) {
block('see', docs.see, false);
}
if (docs.stability && shouldMentionStability(docs.stability)) {
block('stability', docs.stability, false);
}
if (docs.subclassable) {
block('subclassable', 'Yes');
}
for (const [k, v] of Object.entries(docs.custom ?? {})) {
block(k, v, false);
}
if (docs.example) {
brk();
lines.push('Example::');
lines.push('');
const exampleText = this.convertExample(docs.example, apiLocation);
for (const line of exampleText.split('\n')) {
lines.push(` ${line}`);
}
brk();
}
while (lines.length > 0 && lines[lines.length - 1] === '') {
lines.pop();
}
if (lines.length === 0) {
return;
}
if (lines.length === 1) {
code.line(`${DOCSTRING_QUOTES}${lines[0]}${DOCSTRING_QUOTES}`);
}
else {
code.line(`${DOCSTRING_QUOTES}${lines[0]}`);
lines.splice(0, 1);
for (const line of lines) {
code.line(line);
}
code.line(DOCSTRING_QUOTES);
}
if (options.trailingNewLine) {
code.line();
}
}
convertExample(example, apiLoc) {
const translated = this.rosetta.translateExample(apiLoc, example, jsii_rosetta_1.TargetLanguage.PYTHON, (0, jsii_rosetta_1.enforcesStrictMode)(this.assembly));
return translated.source;
}
convertMarkdown(markdown, apiLoc) {
return this.rosetta.translateSnippetsInMarkdown(apiLoc, markdown, jsii_rosetta_1.TargetLanguage.PYTHON, (0, jsii_rosetta_1.enforcesStrictMode)(this.assembly));
}
getPythonType(fqn) {
const type = this.types.get(fqn);
if (type === undefined) {
throw new Error(`Could not locate type: "${fqn}"`);
}
return type;
}
getAssemblyOutputDir(assm) {
return path.join('src', pythonModuleNameToFilename(this.getAssemblyModuleName(assm)));
}
onBeginAssembly(assm, _fingerprint) {
this.package = new Package(assm.targets.python.distName, (0, version_utils_1.toReleaseVersion)(assm.version, index_1.TargetName.PYTHON), assm);
// This is the '<packagename>._jsii' module for this assembly
const assemblyModule = new PythonModule(this.getAssemblyModuleName(assm), undefined, {
assembly: assm,
assemblyFilename: this.getAssemblyFileName(),
loadAssembly: true,
package: this.package,
});
this.package.addModule(assemblyModule);
this.package.addData(assemblyModule, this.getAssemblyFileName(), undefined);
}
onEndAssembly(assm, _fingerprint) {
const resolver = new TypeResolver(this.types, (fqn) => this.findModule(fqn), (fqn) => this.findType(fqn));
this.package.write(this.code, {
assembly: assm,
emittedTypes: new Set(),
resolver,
runtimeTypeChecking: this.runtimeTypeChecking,
submodule: assm.name,
typeCheckingHelper: new TypeCheckingHelper(),
typeResolver: (fqn) => resolver.dereference(fqn),
});
}
/**
* Will be called for assembly root, namespaces and submodules (anything that contains other types, based on its FQN)
*/
onBeginNamespace(ns) {
// 'ns' contains something like '@scope/jsii-calc-base-of-base'
const submoduleLike = ns === this.assembly.name
? this.assembly
: this.assembly.submodules?.[ns];
const readmeLocation = { api: 'moduleReadme', moduleFqn: ns };
const module = new PythonModule((0, type_name_1.toPackageName)(ns, this.assembly), ns, {
assembly: this.assembly,
assemblyFilename: this.getAssemblyFileName(),
package: this.package,
moduleDocumentation: submoduleLike?.readme
? this.convertMarkdown(submoduleLike.readme?.markdown, readmeLocation).trim()
: undefined,
});
this.package.addModule(module);
this.types.set(ns, module);
if (ns === this.assembly.name) {
// This applies recursively to submodules, so no need to duplicate!
this.package.addData(module, 'py.typed', '');
}
if (ns === this.assembly.name) {
this.rootModule = module;
}
else {
this.rootModule.addPythonModule(module);
}
}
onEndNamespace(ns) {
if (ns === this.assembly.name) {
delete this.rootModule;
}
}
onBeginClass(cls, abstract) {
const klass = new Class(this, (0, util_2.toPythonIdentifier)(cls.name), cls, cls.fqn, {
abstract,
bases: cls.base ? [this.findType(cls.base)] : undefined,
interfaces: cls.interfaces?.map((base) => this.findType(base)),
abstractBases: abstract ? this.getAbstractBases(cls) : [],
}, cls.docs);
if (cls.initializer !== undefined) {
const { parameters = [] } = cls.initializer;
klass.addMember(new Initializer(this, '__init__', undefined, parameters, undefined, cls.initializer.docs, false, // Never static
klass, { liftedProp: this.getliftedProp(cls.initializer), parent: cls }));
}
this.addPythonType(klass);
}
onStaticMethod(cls, method) {
const { parameters = [] } = method;
const klass = this.getPythonType(cls.fqn);
klass.addMember(new StaticMethod(this, toPythonMethodName(method.name), method.name, parameters, method.returns, method.docs, true, // Always static
klass, {
abstract: method.abstract,
liftedProp: this.getliftedProp(method),
parent: cls,
}));
}
onStaticProperty(cls, prop) {
const klass = this.getPythonType(cls.fqn);
klass.addMember(new StaticProperty(this, toPythonPropertyName(prop.name, prop.const), prop.name, prop, prop.docs, klass, {
abstract: prop.abstract,
immutable: prop.immutable,
isStatic: prop.static,
parent: cls,
}));
}
onMethod(cls, method) {
const { parameters = [] } = method;
const klass = this.getPythonType(cls.fqn);
if (method.async) {
klass.addMember(new AsyncMethod(this, toPythonMethodName(method.name, method.protected), method.name, parameters, method.returns, method.docs, !!method.static, klass, {
abstract: method.abstract,
liftedProp: this.getliftedProp(method),
parent: cls,
}));
}
else {
klass.addMember(new Method(this, toPythonMethodName(method.name, method.protected), method.name, parameters, method.returns, method.docs, !!method.static, klass, {
abstract: method.abstract,
liftedProp: this.getliftedProp(method),
parent: cls,
}));
}
}
onProperty(cls, prop) {
const klass = this.getPythonType(cls.fqn);
klass.addMember(new Property(this, toPythonPropertyName(prop.name, prop.const, prop.protected), prop.name, prop, prop.docs, klass, {
abstract: prop.abstract,
immutable: prop.immutable,
isStatic: prop.static,
parent: cls,
}));
}
onUnionProperty(cls, prop, _union) {
this.onProperty(cls, prop);
}
onBeginInterface(ifc) {
let iface;
if (ifc.datatype) {
iface = new Struct(this, (0, util_2.toPythonIdentifier)(ifc.name), ifc, ifc.fqn, { bases: ifc.interfaces?.map((base) => this.findType(base)) }, ifc.docs);
}
else {
iface = new Interface(this, (0, util_2.toPythonIdentifier)(ifc.name), ifc, ifc.fqn, { bases: ifc.interfaces?.map((base) => this.findType(base)) }, ifc.docs);
}
this.addPythonType(iface);
}
onEndInterface(_ifc) {
return;
}
onInterfaceMethod(ifc, method) {
const { parameters = [] } = method;
const klass = this.getPythonType(ifc.fqn);
klass.addMember(new InterfaceMethod(this, toPythonMethodName(method.name, method.protected), method.name, parameters, method.returns, method.docs, !!method.static, klass, { liftedProp: this.getliftedProp(method), parent: ifc }));
}
onInterfaceProperty(ifc, prop) {
let ifaceProperty;
const klass = this.getPythonType(ifc.fqn);
if (ifc.datatype) {
ifaceProperty = new StructField(this, prop, ifc);
}
else {
ifaceProperty = new InterfaceProperty(this, toPythonPropertyName(prop.name, prop.const, prop.protected), prop.name, prop, prop.docs, klass, { immutable: prop.immutable, isStatic: prop.static, parent: ifc });
}
klass.addMember(ifaceProperty);
}
onBeginEnum(enm) {
this.addPythonType(new Enum(this, (0, util_2.toPythonIdentifier)(enm.name), enm, enm.fqn, {}, enm.docs));
}
onEnumMember(enm, member) {
this.getPythonType(enm.fqn).addMember(new EnumMember(this, (0, util_2.toPythonIdentifier)(member.name), member.name, member.docs, enm));
}
onInterfaceMethodOverload(_ifc, _overload, _originalMethod) {
throw new Error('Unhandled Type: InterfaceMethodOverload');
}
onMethodOverload(_cls, _overload, _originalMethod) {
throw new Error('Unhandled Type: MethodOverload');
}
onStaticMethodOverload(_cls, _overload, _originalMethod) {
throw new Error('Unhandled Type: StaticMethodOverload');
}
getAssemblyModuleName(assm) {
return `${assm.targets.python.module}._jsii`;
}
getParentFQN(fqn) {
const m = /^(.+)\.[^.]+$/.exec(fqn);
if (m == null) {
throw new Error(`Could not determine parent FQN of: ${fqn}`);
}
return m[1];
}
getParent(fqn) {
return this.getPythonType(this.getParentFQN(fqn));
}
addPythonType(type) {
if (type.fqn == null) {
throw new Error('Cannot add a Python type without a FQN.');
}
this.getParent(type.fqn).addMember(type);
this.types.set(type.fqn, type);
}
getliftedProp(method) {
// If there are parameters to this method, and if the last parameter's type is
// a datatype interface, then we want to lift the members of that last paramter
// as keyword arguments to this function.
if (method.parameters?.length ?? 0 >= 1) {
const lastParameter = method.parameters.slice(-1)[0];
if (!lastParameter.variadic &&
spec.isNamedTypeReference(lastParameter.type)) {
const lastParameterType = this.findType(lastParameter.type.fqn);
if (spec.isInterfaceType(lastParameterType) &&
lastParameterType.datatype) {
return lastParameterType;
}
}
}
return undefined;
}
getAbstractBases(cls) {
const abstractBases = [];
if (cls.base !== undefined) {
const base = this.findType(cls.base);
if (!spec.isClassType(base)) {
throw new Error("Class inheritance that isn't a class?");
}
if (base.abstract) {
abstractBases.push(base);
}
}
return abstractBases;
}
}
/**
* Render a one-line description of the given docs, used for method arguments and inlined properties
*/
function onelineDescription(docs) {
// Only consider a subset of fields here, we don't have a lot of formatting space
if (!docs || Object.keys(docs).length === 0) {
return '-';
}
const parts = [];
if (docs.summary) {
parts.push((0, markdown_1.md2rst)((0, _utils_1.renderSummary)(docs)));
}
if (docs.remarks) {
parts.push((0, markdown_1.md2rst)(docs.remarks));
}
if (docs.default) {
parts.push(`Default: ${(0, markdown_1.md2rst)(docs.default)}`);
}
return parts.join(' ').replace(/\s+/g, ' ');
}
function shouldMentionStability(s) {
// Don't render "stable" or "external", those are both stable by implication.
return s === spec.Stability.Deprecated || s === spec.Stability.Experimental;
}
function isStruct(typeSystem, ref) {
if (!spec.isNamedTypeReference(ref)) {
return false;
}
const type = typeSystem.tryFindFqn(ref.fqn);
return !!(type?.isInterfaceType() && type?.isDataType());
}
/**
* Appends `_` at the end of `name` until it no longer conflicts with any of the
* entries in `inUse`.
*
* @param name the name to be slugified.
* @param inUse the names that are already being used.
*
* @returns the slugified name.
*/
function slugifyAsNeeded(name, inUse) {
const inUseSet = new Set(inUse);
while (inUseSet.has(name)) {
name = `${name}_`;
}
return name;
}
////////////////////////////////////////////////////////////////////////////////
// BEHOLD: Helpers to output code that looks like what Black would format into...
//
// @see https://black.readthedocs.io/en/stable/the_black_code_style.html
const TARGET_LINE_LENGTH = 88;
function openSignature(code, keyword, name, params, returnType, lineComment) {
const prefix = `${keyword} ${name}`;
const suffix = returnType ? ` -> ${returnType}` : '';
if (params.length === 0) {
code.openBlock(`${prefix}${returnType ? '()' : ''}${suffix}`);
return;
}
const join = ', ';
const { elementsSize, joinSize } = totalSizeOf(params, join);
const hasComments = params.some((param) => /#\s*.+$/.exec(param) != null);
if (!hasComments &&
TARGET_LINE_LENGTH >
code.currentIndentLength +
prefix.length +
elementsSize +
joinSize +
suffix.length +
2) {
code.indent(`${prefix}(${params.join(join)})${suffix}:${lineComment ? ` # ${lineComment}` : ''}`);
return;
}
code.indent(`${prefix}(`);
for (const param of params) {
code.line(param.replace(/(\s*# .+)?$/, ',$1'));
}
code.unindent(false);
code.indent(`)${suffix}:${lineComment ? ` # ${lineComment}` : ''}`);
}
/**
* Emits runtime type checking code for parameters.
*
* @param code the CodeMaker to use for emitting code.
* @param context the emit context used when emitting this code.
* @param params the parameter signatures to be type-checked.
* @params pythonName the name of the Python function being checked (qualified).
*/
function emitParameterTypeChecks(code, context, params, fqn) {
if (!context.runtimeTypeChecking) {
return false;
}
const paramInfo = params.map((param) => {
const [name] = param.split(/\s*[:=#]\s*/, 1);
if (name === '*') {
return { kwargsMark: true };
}
else if (name.startsWith('*')) {
return { name: name.slice(1), is_rest: true };
}
return { name };
});
const paramNames = paramInfo
.filter((param) => param.name != null)
.map((param) => param.name.split(/\s*:\s*/)[0]);
const typesVar = slugifyAsNeeded('type_hints', paramNames);
let openedBlock = false;
for (const { is_rest, kwargsMark, name } of paramInfo) {
if (kwargsMark) {
if (!context.runtimeTypeCheckKwargs) {
// This is the keyword-args separator, we won't check keyword arguments here because the kwargs will be rolled
// up into a struct instance, and that struct's constructor will be checking again...
break;
}
// Skip this (there is nothing to be checked as this is just a marker...)
continue;
}
if (!openedBlock) {
code.openBlock('if __debug__');
code.line(`${typesVar} = ${context.typeCheckingHelper.getTypeHints(fqn, params)}`);
openedBlock = true;
}
let expectedType = `${typesVar}[${JSON.stringify(name)}]`;
let comment = '';
if (is_rest) {
// This is a vararg, so the value will appear as a tuple.
expectedType = `typing.Tuple[${expectedType}, ...]`;
// Need to ignore reportGeneralTypeIssues because pyright incorrectly parses that as a type annotation 😒
comment = ' # pyright: ignore [reportGeneralTypeIssues]';
}
code.line(`check_type(argname=${JSON.stringify(`argument ${name}`)}, value=${name}, expected_type=${expectedType})${comment}`);
}
if (openedBlock) {
code.closeBlock();
return true;
}
// We did not reference type annotations data if we never opened a type-checking block.
return false;
}
function assignCallResult(code, variable, funct, params) {
const prefix = `${variable} = ${funct}(`;
const suffix = ')';
if (params.length === 0) {
code.line(`${prefix}${suffix}`);
return;
}
const join = ', ';
const { elementsSize, joinSize } = totalSizeOf(params, join);
if (TARGET_LINE_LENGTH >
code.currentIndentLength +
prefix.length +
elementsSize +
joinSize +
suffix.length) {
code.line(`${prefix}${params.join(join)}${suffix}`);
return;
}
code.indent(prefix);
if (TARGET_LINE_LENGTH > code.currentIndentLength + elementsSize + joinSize) {
code.line(params.join(join));
}
else {
for (const param of params) {
code.line(`${param},`);
}
}
code.unindent(suffix);
}
function assignDictionary(code, variable, elements, trailing, compact = false) {
const space = compact ? '' : ' ';
const prefix = `${variable}${space}=${space}{`;
const suffix = `}${trailing ?? ''}`;
if (elements.length === 0) {
code.line(`${prefix}${suffix}`);
return;
}
if (compact) {
const join = ', ';
const { elementsSize, joinSize } = totalSizeOf(elements, join);
if (TARGET_LINE_LENGTH >
prefix.length +
code.currentIndentLength +
elementsSize +
joinSize +
suffix.length) {
code.line(`${prefix}${elements.join(join)}${suffix}`);
return;
}
}
code.indent(prefix);
for (const elt of elements) {
code.line(`${elt},`);
}
code.unindent(suffix);
}
function emitList(code, prefix, elements, suffix, opts) {
if (elements.length === 0) {
code.line(`${prefix}${suffix}`);
return;
}
const join = ', ';
const { elementsSize, joinSize } = totalSizeOf(elements, join);
if (TARGET_LINE_LENGTH >
code.currentIndentLength +
prefix.length +
elementsSize +
joinSize +
suffix.length) {
code.line(`${prefix}${elements.join(join)}${suffix}`);
return;
}
const [before, after] = opts?.ifMulti ?? ['', ''];
code.indent(`${prefix}${before}`);
if (elements.length === 1) {
code.line(elements[0]);
}
else {
if (TARGET_LINE_LENGTH >
code.currentIndentLength + elementsSize + joinSize) {
code.line(elements.join(join));
}
else {
for (const elt of elements) {
code.line(`${elt},`);
}
}
}
code.unindent(`${after}${suffix}`);
}
function totalSizeOf(strings, join) {
return {
elementsSize: strings
.map((str) => str.length)
.reduce((acc, elt) => acc + elt, 0),
joinSize: strings.length > 1 ? join.length * (strings.length - 1) : 0,
};
}
function nestedContext(context, fqn) {
return {
...context,
surroundingTypeFqns: fqn != null
? [...(context.surroundingTypeFqns ?? []), fqn]
: context.surroundingTypeFqns,
};
}
const isDeprecated = (x) => x.docs?.deprecated !== undefined;
/**
* Last component of a .-separated name
*/
function lastComponent(n) {
const parts = n.split('.');
return parts[parts.length - 1];
}
//# sourceMappingURL=python.js.map