public emit()

in packages/jsii-pacmak/lib/targets/python.ts [1693:1872]


  public emit(code: CodeMaker, context: EmitContext) {
    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('import typeguard');
    code.line(
      'from importlib.metadata import version as _metadata_package_version',
    );
    code.line(
      "TYPEGUARD_MAJOR_VERSION = int(_metadata_package_version('typeguard').split('.')[0])",
    );
    code.line();

    code.openBlock(
      'def check_type(argname: str, value: object, expected_type: typing.Any) -> typing.Any',
    );
    code.openBlock('if TYPEGUARD_MAJOR_VERSION <= 2');
    code.line(
      'return typeguard.check_type(argname=argname, value=value, expected_type=expected_type) # type:ignore',
    );
    code.closeBlock();
    code.openBlock('else');
    code.line(
      'if isinstance(value, jsii._reference_map.InterfaceDynamicProxy): # pyright: ignore [reportAttributeAccessIssue]',
    );
    code.line('   pass');
    code.openBlock('else');
    code.openBlock('if TYPEGUARD_MAJOR_VERSION == 3');
    code.line(
      'typeguard.config.collection_check_strategy = typeguard.CollectionCheckStrategy.ALL_ITEMS # type:ignore',
    );
    code.line(
      'typeguard.check_type(value=value, expected_type=expected_type) # type:ignore',
    );
    code.closeBlock();
    code.openBlock('else');
    code.line(
      'typeguard.check_type(value=value, expected_type=expected_type, collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS) # type:ignore',
    );
    code.closeBlock();
    code.closeBlock();
    code.closeBlock();
    code.closeBlock();

    // 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}`);
      }
    }
  }