github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/python/gen.go (about)

     1  // Copyright 2016-2021, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
    16  // goconst linter's warning.
    17  //
    18  // nolint: lll, goconst
    19  package python
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io"
    25  	"path"
    26  	"path/filepath"
    27  	"reflect"
    28  	"regexp"
    29  	"sort"
    30  	"strconv"
    31  	"strings"
    32  	"unicode"
    33  
    34  	"github.com/blang/semver"
    35  
    36  	"github.com/pulumi/pulumi/pkg/v3/codegen"
    37  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    38  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
    39  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    40  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
    41  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    42  )
    43  
    44  type typeDetails struct {
    45  	outputType         bool
    46  	inputType          bool
    47  	resourceOutputType bool
    48  	plainType          bool
    49  }
    50  
    51  type imports codegen.StringSet
    52  
    53  func (imports imports) addType(mod *modContext, t *schema.ObjectType, input bool) {
    54  	imports.addTypeIf(mod, t, input, nil /*predicate*/)
    55  }
    56  
    57  func (imports imports) addTypeIf(mod *modContext, t *schema.ObjectType, input bool, predicate func(imp string) bool) {
    58  	if imp := mod.importObjectType(t, input); imp != "" && (predicate == nil || predicate(imp)) {
    59  		codegen.StringSet(imports).Add(imp)
    60  	}
    61  }
    62  
    63  func (imports imports) addEnum(mod *modContext, enum *schema.EnumType) {
    64  	if imp := mod.importEnumType(enum); imp != "" {
    65  		codegen.StringSet(imports).Add(imp)
    66  	}
    67  }
    68  
    69  func (imports imports) addResource(mod *modContext, r *schema.ResourceType) {
    70  	if imp := mod.importResourceType(r); imp != "" {
    71  		codegen.StringSet(imports).Add(imp)
    72  	}
    73  }
    74  
    75  func (imports imports) strings() []string {
    76  	result := make([]string, 0, len(imports))
    77  	for imp := range imports {
    78  		result = append(result, imp)
    79  	}
    80  	sort.Strings(result)
    81  	return result
    82  }
    83  
    84  func title(s string) string {
    85  	if s == "" {
    86  		return ""
    87  	}
    88  	runes := []rune(s)
    89  	return string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...))
    90  }
    91  
    92  type modLocator struct {
    93  	// Returns defining modlue for a given ObjectType. Returns nil
    94  	// for types that are not being generated in the current
    95  	// GeneratePacakge call.
    96  	objectTypeMod func(*schema.ObjectType) *modContext
    97  }
    98  
    99  type modContext struct {
   100  	pkg              *schema.Package
   101  	modLocator       *modLocator
   102  	mod              string
   103  	pyPkgName        string
   104  	types            []*schema.ObjectType
   105  	enums            []*schema.EnumType
   106  	resources        []*schema.Resource
   107  	functions        []*schema.Function
   108  	typeDetails      map[*schema.ObjectType]*typeDetails
   109  	children         []*modContext
   110  	parent           *modContext
   111  	tool             string
   112  	extraSourceFiles []string
   113  	isConfig         bool
   114  
   115  	// Name overrides set in PackageInfo
   116  	modNameOverrides map[string]string // Optional overrides for Pulumi module names
   117  	compatibility    string            // Toggle compatibility mode for a specified target.
   118  
   119  	// Determine whether to lift single-value method return values
   120  	liftSingleValueMethodReturns bool
   121  }
   122  
   123  func (mod *modContext) isTopLevel() bool {
   124  	return mod.parent == nil
   125  }
   126  
   127  func (mod *modContext) walkSelfWithDescendants() []*modContext {
   128  	var found []*modContext
   129  	found = append(found, mod)
   130  	for _, childMod := range mod.children {
   131  		found = append(found, childMod.walkSelfWithDescendants()...)
   132  	}
   133  	return found
   134  }
   135  
   136  func (mod *modContext) addChild(child *modContext) {
   137  	mod.children = append(mod.children, child)
   138  	child.parent = mod
   139  }
   140  
   141  func (mod *modContext) details(t *schema.ObjectType) *typeDetails {
   142  	m := mod
   143  
   144  	if mod.modLocator != nil {
   145  		if actualMod := mod.modLocator.objectTypeMod(t); actualMod != nil {
   146  			m = actualMod
   147  		}
   148  	}
   149  
   150  	details, ok := m.typeDetails[t]
   151  	if !ok {
   152  		details = &typeDetails{}
   153  		if m.typeDetails == nil {
   154  			m.typeDetails = map[*schema.ObjectType]*typeDetails{}
   155  		}
   156  		m.typeDetails[t] = details
   157  	}
   158  	return details
   159  }
   160  
   161  func (mod *modContext) modNameAndName(pkg *schema.Package, t schema.Type, input bool) (modName string, name string) {
   162  	var info PackageInfo
   163  	contract.AssertNoError(pkg.ImportLanguages(map[string]schema.Language{"python": Importer}))
   164  	if v, ok := pkg.Language["python"].(PackageInfo); ok {
   165  		info = v
   166  	}
   167  
   168  	var token string
   169  	switch t := t.(type) {
   170  	case *schema.EnumType:
   171  		token, name = t.Token, tokenToName(t.Token)
   172  	case *schema.ObjectType:
   173  		namingCtx := &modContext{
   174  			pkg:              pkg,
   175  			modNameOverrides: info.ModuleNameOverrides,
   176  			compatibility:    info.Compatibility,
   177  		}
   178  		token, name = t.Token, namingCtx.unqualifiedObjectTypeName(t, input)
   179  	case *schema.ResourceType:
   180  		token, name = t.Token, tokenToName(t.Token)
   181  	}
   182  
   183  	modName = tokenToModule(token, pkg, info.ModuleNameOverrides)
   184  	if modName != "" {
   185  		modName = strings.ReplaceAll(modName, "/", ".") + "."
   186  	}
   187  	return
   188  }
   189  
   190  func (mod *modContext) unqualifiedObjectTypeName(t *schema.ObjectType, input bool) string {
   191  	name := tokenToName(t.Token)
   192  
   193  	if mod.compatibility != tfbridge20 && mod.compatibility != kubernetes20 {
   194  		if t.IsInputShape() {
   195  			return name + "Args"
   196  		}
   197  		return name
   198  	}
   199  
   200  	switch {
   201  	case input:
   202  		return name + "Args"
   203  	case mod.details(t).plainType:
   204  		return name + "Result"
   205  	}
   206  	return name
   207  }
   208  
   209  func (mod *modContext) objectType(t *schema.ObjectType, input bool) string {
   210  	var prefix string
   211  	if !input {
   212  		prefix = "outputs."
   213  	}
   214  
   215  	// If it's an external type, reference it via fully qualified name.
   216  	if t.Package != mod.pkg {
   217  		modName, name := mod.modNameAndName(t.Package, t, input)
   218  		return fmt.Sprintf("'%s.%s%s%s'", pyPack(t.Package.Name), modName, prefix, name)
   219  	}
   220  
   221  	modName, name := mod.tokenToModule(t.Token), mod.unqualifiedObjectTypeName(t, input)
   222  	if modName == "" && modName != mod.mod {
   223  		rootModName := "_root_outputs."
   224  		if input {
   225  			rootModName = "_root_inputs."
   226  		}
   227  		return fmt.Sprintf("'%s%s'", rootModName, name)
   228  	}
   229  
   230  	if modName == mod.mod {
   231  		modName = ""
   232  	}
   233  	if modName != "" {
   234  		modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
   235  	}
   236  
   237  	return fmt.Sprintf("'%s%s%s'", modName, prefix, name)
   238  }
   239  
   240  func (mod *modContext) tokenToEnum(tok string) string {
   241  	modName, name := mod.tokenToModule(tok), tokenToName(tok)
   242  
   243  	if modName == "" && modName != mod.mod {
   244  		return fmt.Sprintf("'_root_enums.%s'", name)
   245  	}
   246  
   247  	if modName == mod.mod {
   248  		modName = ""
   249  	}
   250  	if modName != "" {
   251  		modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
   252  	}
   253  
   254  	return fmt.Sprintf("'%s%s'", modName, name)
   255  }
   256  
   257  func (mod *modContext) resourceType(r *schema.ResourceType) string {
   258  	if r.Resource == nil || r.Resource.Package == mod.pkg {
   259  		return mod.tokenToResource(r.Token)
   260  	}
   261  
   262  	// Is it a provider resource?
   263  	if strings.HasPrefix(r.Token, "pulumi:providers:") {
   264  		pkgName := strings.TrimPrefix(r.Token, "pulumi:providers:")
   265  		return fmt.Sprintf("pulumi_%s.Provider", pkgName)
   266  	}
   267  
   268  	pkg := r.Resource.Package
   269  	modName, name := mod.modNameAndName(pkg, r, false)
   270  	return fmt.Sprintf("%s.%s%s", pyPack(pkg.Name), modName, name)
   271  }
   272  
   273  func (mod *modContext) tokenToResource(tok string) string {
   274  	// token := pkg : module : member
   275  	// module := path/to/module
   276  
   277  	components := strings.Split(tok, ":")
   278  	contract.Assertf(len(components) == 3, "malformed token %v", tok)
   279  
   280  	// Is it a provider resource?
   281  	if components[0] == "pulumi" && components[1] == "providers" {
   282  		return fmt.Sprintf("pulumi_%s.Provider", components[2])
   283  	}
   284  
   285  	modName, name := mod.tokenToModule(tok), tokenToName(tok)
   286  
   287  	if modName == mod.mod {
   288  		modName = ""
   289  	}
   290  	if modName != "" {
   291  		modName = "_" + strings.ReplaceAll(modName, "/", ".") + "."
   292  	}
   293  
   294  	return fmt.Sprintf("%s%s", modName, name)
   295  }
   296  
   297  func tokenToName(tok string) string {
   298  	// token := pkg : module : member
   299  	// module := path/to/module
   300  
   301  	components := strings.Split(tok, ":")
   302  	contract.Assertf(len(components) == 3, "malformed token %v", tok)
   303  
   304  	return title(components[2])
   305  }
   306  
   307  func tokenToModule(tok string, pkg *schema.Package, moduleNameOverrides map[string]string) string {
   308  	// See if there's a manually-overridden module name.
   309  	canonicalModName := pkg.TokenToModule(tok)
   310  	if override, ok := moduleNameOverrides[canonicalModName]; ok {
   311  		return override
   312  	}
   313  	// A module can include fileparts, which we want to preserve.
   314  	var modName string
   315  	for i, part := range strings.Split(strings.ToLower(canonicalModName), "/") {
   316  		if i > 0 {
   317  			modName += "/"
   318  		}
   319  		modName += PyName(part)
   320  	}
   321  	return modName
   322  }
   323  
   324  func (mod *modContext) tokenToModule(tok string) string {
   325  	return tokenToModule(tok, mod.pkg, mod.modNameOverrides)
   326  }
   327  
   328  func printComment(w io.Writer, comment string, indent string) {
   329  	lines := strings.Split(comment, "\n")
   330  	for len(lines) > 0 && lines[len(lines)-1] == "" {
   331  		lines = lines[:len(lines)-1]
   332  	}
   333  	if len(lines) == 0 {
   334  		return
   335  	}
   336  
   337  	// Known special characters that need escaping.
   338  	replacer := strings.NewReplacer(`\`, `\\`, `"""`, `\"\"\"`)
   339  	fmt.Fprintf(w, "%s\"\"\"\n", indent)
   340  	for _, l := range lines {
   341  		if l == "" {
   342  			fmt.Fprintf(w, "\n")
   343  		} else {
   344  			escaped := replacer.Replace(l)
   345  			fmt.Fprintf(w, "%s%s\n", indent, escaped)
   346  		}
   347  	}
   348  	fmt.Fprintf(w, "%s\"\"\"\n", indent)
   349  }
   350  
   351  func genStandardHeader(w io.Writer, tool string) {
   352  	// Set the encoding to UTF-8, in case the comments contain non-ASCII characters.
   353  	fmt.Fprintf(w, "# coding=utf-8\n")
   354  
   355  	// Emit a standard warning header ("do not edit", etc).
   356  	fmt.Fprintf(w, "# *** WARNING: this file was generated by %v. ***\n", tool)
   357  	fmt.Fprintf(w, "# *** Do not edit by hand unless you're certain you know what you are doing! ***\n\n")
   358  }
   359  
   360  func (mod *modContext) genHeader(w io.Writer, needsSDK bool, imports imports) {
   361  	genStandardHeader(w, mod.tool)
   362  
   363  	// If needed, emit the standard Pulumi SDK import statement.
   364  	if needsSDK {
   365  		rel, err := filepath.Rel(mod.mod, "")
   366  		contract.Assert(err == nil)
   367  		relRoot := path.Dir(rel)
   368  		relImport := relPathToRelImport(relRoot)
   369  
   370  		fmt.Fprintf(w, "import copy\n")
   371  		fmt.Fprintf(w, "import warnings\n")
   372  		fmt.Fprintf(w, "import pulumi\n")
   373  		fmt.Fprintf(w, "import pulumi.runtime\n")
   374  		fmt.Fprintf(w, "from typing import Any, Mapping, Optional, Sequence, Union, overload\n")
   375  		fmt.Fprintf(w, "from %s import _utilities\n", relImport)
   376  		for _, imp := range imports.strings() {
   377  			fmt.Fprintf(w, "%s\n", imp)
   378  		}
   379  		fmt.Fprintf(w, "\n")
   380  	}
   381  }
   382  
   383  func relPathToRelImport(relPath string) string {
   384  	// Convert relative path to relative import e.g. "../.." -> "..."
   385  	// https://realpython.com/absolute-vs-relative-python-imports/#relative-imports
   386  	relImport := "."
   387  	if relPath == "." {
   388  		return relImport
   389  	}
   390  	for _, component := range strings.Split(relPath, "/") {
   391  		if component == ".." {
   392  			relImport += "."
   393  		} else {
   394  			relImport += component
   395  		}
   396  	}
   397  	return relImport
   398  }
   399  
   400  func (mod *modContext) genUtilitiesFile() []byte {
   401  	buffer := &bytes.Buffer{}
   402  	genStandardHeader(buffer, mod.tool)
   403  	fmt.Fprintf(buffer, utilitiesFile)
   404  	optionalURL := "None"
   405  	if url := mod.pkg.PluginDownloadURL; url != "" {
   406  		optionalURL = fmt.Sprintf("%q", url)
   407  	}
   408  	_, err := fmt.Fprintf(buffer, `
   409  def get_plugin_download_url():
   410  	return %s
   411  `, optionalURL)
   412  	contract.AssertNoError(err)
   413  	return buffer.Bytes()
   414  }
   415  
   416  func (mod *modContext) gen(fs codegen.Fs) error {
   417  	dir := path.Join(mod.pyPkgName, mod.mod)
   418  
   419  	var exports []string
   420  	for p := range fs {
   421  		d := path.Dir(p)
   422  		if d == "." {
   423  			d = ""
   424  		}
   425  		if d == dir {
   426  			exports = append(exports, strings.TrimSuffix(path.Base(p), ".py"))
   427  		}
   428  	}
   429  
   430  	addFile := func(name, contents string) {
   431  		p := path.Join(dir, name)
   432  		if !strings.HasSuffix(name, ".pyi") {
   433  			exports = append(exports, name[:len(name)-len(".py")])
   434  		}
   435  		fs.Add(p, []byte(contents))
   436  	}
   437  
   438  	// Utilities, config, readme
   439  	switch mod.mod {
   440  	case "":
   441  		fs.Add(filepath.Join(dir, "_utilities.py"), mod.genUtilitiesFile())
   442  		fs.Add(filepath.Join(dir, "py.typed"), []byte{})
   443  
   444  		// Ensure that the top-level (provider) module directory contains a README.md file.
   445  
   446  		var readme string
   447  		if pythonInfo, ok := mod.pkg.Language["python"]; ok {
   448  			if typedInfo, ok := pythonInfo.(PackageInfo); ok {
   449  				readme = typedInfo.Readme
   450  			}
   451  		}
   452  
   453  		if readme == "" {
   454  			readme = mod.pkg.Description
   455  			if readme != "" && readme[len(readme)-1] != '\n' {
   456  				readme += "\n"
   457  			}
   458  			if mod.pkg.Attribution != "" {
   459  				if len(readme) != 0 {
   460  					readme += "\n"
   461  				}
   462  				readme += mod.pkg.Attribution
   463  			}
   464  			if readme != "" && readme[len(readme)-1] != '\n' {
   465  				readme += "\n"
   466  			}
   467  		}
   468  		fs.Add(filepath.Join(dir, "README.md"), []byte(readme))
   469  
   470  	case "config":
   471  		if len(mod.pkg.Config) > 0 {
   472  			vars, err := mod.genConfig(mod.pkg.Config)
   473  			if err != nil {
   474  				return err
   475  			}
   476  			addFile("vars.py", vars)
   477  			typeStubs, err := mod.genConfigStubs(mod.pkg.Config)
   478  			if err != nil {
   479  				return err
   480  			}
   481  			addFile("__init__.pyi", typeStubs)
   482  		}
   483  	}
   484  
   485  	// Resources
   486  	for _, r := range mod.resources {
   487  		if r.IsOverlay {
   488  			// This resource code is generated by the provider, so no further action is required.
   489  			continue
   490  		}
   491  
   492  		res, err := mod.genResource(r)
   493  		if err != nil {
   494  			return err
   495  		}
   496  		name := PyName(tokenToName(r.Token))
   497  		if mod.compatibility == kubernetes20 {
   498  			// To maintain backward compatibility for kubernetes, the file names
   499  			// need to be CamelCase instead of the standard snake_case.
   500  			name = tokenToName(r.Token)
   501  		}
   502  		if r.IsProvider {
   503  			name = "provider"
   504  		}
   505  		addFile(name+".py", res)
   506  	}
   507  
   508  	// Functions
   509  	for _, f := range mod.functions {
   510  		if f.IsOverlay {
   511  			// This function code is generated by the provider, so no further action is required.
   512  			continue
   513  		}
   514  
   515  		fun, err := mod.genFunction(f)
   516  		if err != nil {
   517  			return err
   518  		}
   519  		addFile(PyName(tokenToName(f.Token))+".py", fun)
   520  	}
   521  
   522  	// Nested types
   523  	if len(mod.types) > 0 {
   524  		if err := mod.genTypes(dir, fs); err != nil {
   525  			return err
   526  		}
   527  	}
   528  
   529  	// Enums
   530  	if len(mod.enums) > 0 {
   531  		buffer := &bytes.Buffer{}
   532  		if err := mod.genEnums(buffer, mod.enums); err != nil {
   533  			return err
   534  		}
   535  
   536  		addFile("_enums.py", buffer.String())
   537  	}
   538  
   539  	// Index
   540  	if !mod.isEmpty() {
   541  		fs.Add(path.Join(dir, "__init__.py"), []byte(mod.genInit(exports)))
   542  	}
   543  
   544  	return nil
   545  }
   546  
   547  func (mod *modContext) hasTypes(input bool) bool {
   548  	if allTypesAreOverlays(mod.types) {
   549  		return false
   550  	}
   551  	for _, t := range mod.types {
   552  		if input && mod.details(t).inputType {
   553  			return true
   554  		}
   555  		if !input && mod.details(t).outputType {
   556  			return true
   557  		}
   558  	}
   559  	return false
   560  }
   561  
   562  func (mod *modContext) isEmpty() bool {
   563  	if len(mod.extraSourceFiles) > 0 || len(mod.functions) > 0 || len(mod.resources) > 0 || len(mod.types) > 0 ||
   564  		mod.isConfig {
   565  		return false
   566  	}
   567  	for _, child := range mod.children {
   568  		if !child.isEmpty() {
   569  			return false
   570  		}
   571  	}
   572  	return true
   573  }
   574  
   575  func (mod *modContext) submodulesExist() bool {
   576  	for _, submod := range mod.children {
   577  		if !submod.isEmpty() {
   578  			return true
   579  		}
   580  	}
   581  	return false
   582  }
   583  
   584  func (mod *modContext) unqualifiedImportName() string {
   585  	name := mod.mod
   586  
   587  	// Extract version suffix from child modules. Nested versions will have their own __init__.py file.
   588  	// Example: apps/v1beta1 -> v1beta1
   589  	parts := strings.Split(name, "/")
   590  	if len(parts) > 1 {
   591  		name = parts[len(parts)-1]
   592  	}
   593  
   594  	return PyName(name)
   595  }
   596  
   597  func (mod *modContext) fullyQualifiedImportName() string {
   598  	name := mod.unqualifiedImportName()
   599  	if mod.parent == nil && name == "" {
   600  		return mod.pyPkgName
   601  	}
   602  	if mod.parent == nil {
   603  		return fmt.Sprintf("%s.%s", pyPack(mod.pkg.Name), name)
   604  	}
   605  	return fmt.Sprintf("%s.%s", mod.parent.fullyQualifiedImportName(), name)
   606  }
   607  
   608  // genInit emits an __init__.py module, optionally re-exporting other members or submodules.
   609  func (mod *modContext) genInit(exports []string) string {
   610  	w := &bytes.Buffer{}
   611  	mod.genHeader(w, false /*needsSDK*/, nil)
   612  	if mod.isConfig {
   613  		fmt.Fprintf(w, "import sys\n")
   614  		fmt.Fprintf(w, "from .vars import _ExportableConfig\n")
   615  		fmt.Fprintf(w, "\n")
   616  		fmt.Fprintf(w, "sys.modules[__name__].__class__ = _ExportableConfig\n")
   617  		return w.String()
   618  	}
   619  	fmt.Fprintf(w, "%s\n", mod.genUtilitiesImport())
   620  	fmt.Fprintf(w, "import typing\n")
   621  
   622  	// Import anything to export flatly that is a direct export rather than sub-module.
   623  	if len(exports) > 0 {
   624  		sort.Slice(exports, func(i, j int) bool {
   625  			return PyName(exports[i]) < PyName(exports[j])
   626  		})
   627  
   628  		fmt.Fprintf(w, "# Export this package's modules as members:\n")
   629  		for _, exp := range exports {
   630  			name := PyName(exp)
   631  			if mod.compatibility == kubernetes20 {
   632  				// To maintain backward compatibility for kubernetes, the file names
   633  				// need to be CamelCase instead of the standard snake_case.
   634  				name = exp
   635  			}
   636  			fmt.Fprintf(w, "from .%s import *\n", name)
   637  		}
   638  	}
   639  	if mod.hasTypes(true /*input*/) {
   640  		fmt.Fprintf(w, "from ._inputs import *\n")
   641  	}
   642  	if mod.hasTypes(false /*input*/) {
   643  		fmt.Fprintf(w, "from . import outputs\n")
   644  	}
   645  
   646  	// If there are subpackages, import them with importlib.
   647  	if mod.submodulesExist() {
   648  
   649  		children := make([]*modContext, len(mod.children))
   650  		copy(children, mod.children)
   651  
   652  		sort.Slice(children, func(i, j int) bool {
   653  			return PyName(children[i].mod) < PyName(children[j].mod)
   654  		})
   655  
   656  		fmt.Fprintf(w, "\n# Make subpackages available:\n")
   657  
   658  		fmt.Fprintf(w, "if typing.TYPE_CHECKING:\n")
   659  
   660  		for _, submod := range children {
   661  			if !submod.isEmpty() {
   662  				unq := submod.unqualifiedImportName()
   663  
   664  				// The `__iam = iam` hack enables
   665  				// PyCharm and VSCode completion to do
   666  				// better.
   667  				//
   668  				// See https://github.com/pulumi/pulumi/issues/7367
   669  				fmt.Fprintf(w, "    import %s as __%s\n    %s = __%s\n",
   670  					submod.fullyQualifiedImportName(),
   671  					unq,
   672  					unq,
   673  					unq)
   674  			}
   675  		}
   676  
   677  		fmt.Fprintf(w, "else:\n")
   678  
   679  		for _, submod := range children {
   680  			if !submod.isEmpty() {
   681  				fmt.Fprintf(w, "    %s = _utilities.lazy_import('%s')\n",
   682  					submod.unqualifiedImportName(),
   683  					submod.fullyQualifiedImportName())
   684  			}
   685  		}
   686  
   687  		fmt.Fprintf(w, "\n")
   688  	}
   689  
   690  	// If there are resources in this module, register the module with the runtime.
   691  	if len(mod.resources) != 0 {
   692  		err := genResourceMappings(mod, w)
   693  		contract.Assert(err == nil)
   694  	}
   695  
   696  	return w.String()
   697  }
   698  
   699  func (mod *modContext) getRelImportFromRoot() string {
   700  	rel, err := filepath.Rel(mod.mod, "")
   701  	contract.Assert(err == nil)
   702  	relRoot := path.Dir(rel)
   703  	return relPathToRelImport(relRoot)
   704  }
   705  
   706  func (mod *modContext) genUtilitiesImport() string {
   707  	rel, err := filepath.Rel(mod.mod, "")
   708  	contract.Assert(err == nil)
   709  	relRoot := path.Dir(rel)
   710  	relImport := relPathToRelImport(relRoot)
   711  	return fmt.Sprintf("from %s import _utilities", relImport)
   712  }
   713  
   714  func (mod *modContext) importObjectType(t *schema.ObjectType, input bool) string {
   715  	if t.Package != mod.pkg {
   716  		return fmt.Sprintf("import %s", pyPack(t.Package.Name))
   717  	}
   718  
   719  	tok := t.Token
   720  	parts := strings.Split(tok, ":")
   721  	contract.Assert(len(parts) == 3)
   722  	refPkgName := parts[0]
   723  
   724  	modName := mod.tokenToModule(tok)
   725  	if modName == mod.mod {
   726  		if input {
   727  			return "from ._inputs import *"
   728  		}
   729  		return "from . import outputs"
   730  	}
   731  
   732  	importPath := mod.getRelImportFromRoot()
   733  	if mod.pkg.Name != parts[0] {
   734  		importPath = fmt.Sprintf("pulumi_%s", refPkgName)
   735  	}
   736  
   737  	if modName == "" {
   738  		imp, as := "outputs", "_root_outputs"
   739  		if input {
   740  			imp, as = "_inputs", "_root_inputs"
   741  		}
   742  		return fmt.Sprintf("from %s import %s as %s", importPath, imp, as)
   743  	}
   744  
   745  	components := strings.Split(modName, "/")
   746  	return fmt.Sprintf("from %s import %[2]s as _%[2]s", importPath, components[0])
   747  }
   748  
   749  func (mod *modContext) importEnumType(e *schema.EnumType) string {
   750  	if e.Package != mod.pkg {
   751  		return fmt.Sprintf("import %s", pyPack(e.Package.Name))
   752  	}
   753  
   754  	modName := mod.tokenToModule(e.Token)
   755  	if modName == mod.mod {
   756  		return "from ._enums import *"
   757  	}
   758  
   759  	importPath := mod.getRelImportFromRoot()
   760  
   761  	if modName == "" {
   762  		return fmt.Sprintf("from %s import _enums as _root_enums", importPath)
   763  	}
   764  
   765  	components := strings.Split(modName, "/")
   766  	return fmt.Sprintf("from %s import %s", importPath, components[0])
   767  }
   768  
   769  func (mod *modContext) importResourceType(r *schema.ResourceType) string {
   770  	if r.Resource != nil && r.Resource.Package != mod.pkg {
   771  		return fmt.Sprintf("import %s", pyPack(r.Resource.Package.Name))
   772  	}
   773  
   774  	tok := r.Token
   775  	parts := strings.Split(tok, ":")
   776  	contract.Assert(len(parts) == 3)
   777  
   778  	// If it's a provider resource, import the top-level package.
   779  	if parts[0] == "pulumi" && parts[1] == "providers" {
   780  		return fmt.Sprintf("import pulumi_%s", parts[2])
   781  	}
   782  
   783  	refPkgName := parts[0]
   784  
   785  	modName := mod.tokenToResource(tok)
   786  
   787  	importPath := mod.getRelImportFromRoot()
   788  	if mod.pkg.Name != parts[0] {
   789  		importPath = fmt.Sprintf("pulumi_%s", refPkgName)
   790  	}
   791  
   792  	name := PyName(tokenToName(r.Token))
   793  	if mod.compatibility == kubernetes20 {
   794  		// To maintain backward compatibility for kubernetes, the file names
   795  		// need to be CamelCase instead of the standard snake_case.
   796  		name = tokenToName(r.Token)
   797  	}
   798  	if r.Resource != nil && r.Resource.IsProvider {
   799  		name = "provider"
   800  	}
   801  
   802  	components := strings.Split(modName, "/")
   803  	return fmt.Sprintf("from %s%s import %s", importPath, name, components[0])
   804  }
   805  
   806  // genConfig emits all config variables in the given module, returning the resulting file.
   807  func (mod *modContext) genConfig(variables []*schema.Property) (string, error) {
   808  	w := &bytes.Buffer{}
   809  
   810  	imports := imports{}
   811  	mod.collectImports(variables, imports, false /*input*/)
   812  
   813  	mod.genHeader(w, true /*needsSDK*/, imports)
   814  	fmt.Fprintf(w, "import types\n")
   815  	fmt.Fprintf(w, "\n")
   816  
   817  	// Create a config bag for the variables to pull from.
   818  	fmt.Fprintf(w, "__config__ = pulumi.Config('%s')\n", mod.pkg.Name)
   819  	fmt.Fprintf(w, "\n\n")
   820  
   821  	// To avoid a breaking change to the existing config getters, we define a class that extends
   822  	// the `ModuleType` type and implements property getters for each config key. We then overwrite
   823  	// the `__class__` attribute of the current module as described in the proposal for PEP-549. This allows
   824  	// us to maintain the existing interface for users but implement dynamic getters behind the scenes.
   825  	fmt.Fprintf(w, "class _ExportableConfig(types.ModuleType):\n")
   826  	indent := "    "
   827  
   828  	// Emit an entry for all config variables.
   829  	for _, p := range variables {
   830  		configFetch, err := genConfigFetch(p)
   831  		if err != nil {
   832  			return "", err
   833  		}
   834  
   835  		typeString := genConfigVarType(p)
   836  		fmt.Fprintf(w, "%s@property\n", indent)
   837  		fmt.Fprintf(w, "%sdef %s(self) -> %s:\n", indent, PyName(p.Name), typeString)
   838  		dblIndent := strings.Repeat(indent, 2)
   839  
   840  		printComment(w, p.Comment, dblIndent)
   841  		fmt.Fprintf(w, "%sreturn %s\n", dblIndent, configFetch)
   842  		fmt.Fprintf(w, "\n")
   843  	}
   844  
   845  	return w.String(), nil
   846  }
   847  
   848  func genConfigFetch(configVar *schema.Property) (string, error) {
   849  	getFunc := "get"
   850  	unwrappedType := codegen.UnwrapType(configVar.Type)
   851  	switch unwrappedType {
   852  	case schema.BoolType:
   853  		getFunc = "get_bool"
   854  	case schema.IntType:
   855  		getFunc = "get_int"
   856  	case schema.NumberType:
   857  		getFunc = "get_float"
   858  	}
   859  
   860  	configFetch := fmt.Sprintf("__config__.%s('%s')", getFunc, configVar.Name)
   861  	if configVar.DefaultValue != nil {
   862  		v, err := getDefaultValue(configVar.DefaultValue, unwrappedType)
   863  		if err != nil {
   864  			return "", err
   865  		}
   866  		configFetch += " or " + v
   867  	}
   868  	return configFetch, nil
   869  }
   870  
   871  func genConfigVarType(configVar *schema.Property) string {
   872  	// For historical reasons and to maintain backwards compatibility, the config variables for python
   873  	// are typed as `Optional[str`] or `str` for complex objects since the getters only use config.get().
   874  	// To return the rich objects would be a breaking change, tracked in https://github.com/pulumi/pulumi/issues/7493
   875  	typeString := "str"
   876  	switch codegen.UnwrapType(configVar.Type) {
   877  	case schema.BoolType:
   878  		typeString = "bool"
   879  	case schema.IntType:
   880  		typeString = "int"
   881  	case schema.NumberType:
   882  		typeString = "float"
   883  	}
   884  
   885  	if configVar.DefaultValue == nil || configVar.DefaultValue.Value == nil {
   886  		typeString = "Optional[" + typeString + "]"
   887  	}
   888  	return typeString
   889  }
   890  
   891  // genConfigStubs emits all type information for the config variables in the given module, returning the resulting file.
   892  // We do this because we lose IDE autocomplete by implementing the dynamic config getters described in genConfig.
   893  // Emitting these stubs allows us to maintain type hints and autocomplete for users.
   894  func (mod *modContext) genConfigStubs(variables []*schema.Property) (string, error) {
   895  	w := &bytes.Buffer{}
   896  
   897  	imports := imports{}
   898  	mod.collectImports(variables, imports, false /*input*/)
   899  
   900  	mod.genHeader(w, true /*needsSDK*/, imports)
   901  
   902  	// Emit an entry for all config variables.
   903  	for _, p := range variables {
   904  		typeString := genConfigVarType(p)
   905  		fmt.Fprintf(w, "%s: %s\n", p.Name, typeString)
   906  		printComment(w, p.Comment, "")
   907  		fmt.Fprintf(w, "\n")
   908  	}
   909  
   910  	return w.String(), nil
   911  }
   912  
   913  func allTypesAreOverlays(types []*schema.ObjectType) bool {
   914  	for _, t := range types {
   915  		if !t.IsOverlay {
   916  			return false
   917  		}
   918  	}
   919  	return true
   920  }
   921  
   922  func (mod *modContext) genTypes(dir string, fs codegen.Fs) error {
   923  	genTypes := func(file string, input bool) error {
   924  		w := &bytes.Buffer{}
   925  
   926  		if allTypesAreOverlays(mod.types) {
   927  			// If all resources in this module are overlays, skip further code generation.
   928  			return nil
   929  		}
   930  
   931  		imports := imports{}
   932  		for _, t := range mod.types {
   933  			if t.IsOverlay {
   934  				// This type is generated by the provider, so no further action is required.
   935  				continue
   936  			}
   937  
   938  			if input && mod.details(t).inputType {
   939  				visitObjectTypes(t.Properties, func(t schema.Type) {
   940  					switch t := t.(type) {
   941  					case *schema.ObjectType:
   942  						imports.addTypeIf(mod, t, true /*input*/, func(imp string) bool {
   943  							// No need to import `._inputs` inside _inputs.py.
   944  							return imp != "from ._inputs import *"
   945  						})
   946  					case *schema.EnumType:
   947  						imports.addEnum(mod, t)
   948  					case *schema.ResourceType:
   949  						imports.addResource(mod, t)
   950  					}
   951  				})
   952  			}
   953  			if !input && mod.details(t).outputType {
   954  				mod.collectImports(t.Properties, imports, false /*input*/)
   955  			}
   956  		}
   957  		for _, e := range mod.enums {
   958  			imports.addEnum(mod, e)
   959  		}
   960  
   961  		mod.genHeader(w, true /*needsSDK*/, imports)
   962  
   963  		// Export only the symbols we want exported.
   964  		fmt.Fprintf(w, "__all__ = [\n")
   965  		for _, t := range mod.types {
   966  			if t.IsOverlay {
   967  				// This type is generated by the provider, so no further action is required.
   968  				continue
   969  			}
   970  
   971  			if input && mod.details(t).inputType || !input && mod.details(t).outputType {
   972  				fmt.Fprintf(w, "    '%s',\n", mod.unqualifiedObjectTypeName(t, input))
   973  			}
   974  		}
   975  		fmt.Fprintf(w, "]\n\n")
   976  
   977  		var hasTypes bool
   978  		for _, t := range mod.types {
   979  			if t.IsOverlay {
   980  				// This type is generated by the provider, so no further action is required.
   981  				continue
   982  			}
   983  
   984  			if input && mod.details(t).inputType {
   985  				if err := mod.genObjectType(w, t, true); err != nil {
   986  					return err
   987  				}
   988  				hasTypes = true
   989  			}
   990  			if !input && mod.details(t).outputType {
   991  				if err := mod.genObjectType(w, t, false); err != nil {
   992  					return err
   993  				}
   994  				hasTypes = true
   995  			}
   996  		}
   997  		if hasTypes {
   998  			fs.Add(path.Join(dir, file), w.Bytes())
   999  		}
  1000  		return nil
  1001  	}
  1002  	if err := genTypes("_inputs.py", true); err != nil {
  1003  		return err
  1004  	}
  1005  	return genTypes("outputs.py", false)
  1006  }
  1007  
  1008  func awaitableTypeNames(tok string) (baseName, awaitableName string) {
  1009  	baseName = pyClassName(tokenToName(tok))
  1010  	awaitableName = "Awaitable" + baseName
  1011  	return
  1012  }
  1013  
  1014  func (mod *modContext) genAwaitableType(w io.Writer, obj *schema.ObjectType) string {
  1015  	baseName, awaitableName := awaitableTypeNames(obj.Token)
  1016  
  1017  	// Produce a class definition with optional """ comment.
  1018  	fmt.Fprint(w, "@pulumi.output_type\n")
  1019  	fmt.Fprintf(w, "class %s:\n", baseName)
  1020  	printComment(w, obj.Comment, "    ")
  1021  
  1022  	// Now generate an initializer with properties for all inputs.
  1023  	fmt.Fprintf(w, "    def __init__(__self__")
  1024  	for _, prop := range obj.Properties {
  1025  		fmt.Fprintf(w, ", %s=None", PyName(prop.Name))
  1026  	}
  1027  	fmt.Fprintf(w, "):\n")
  1028  	if len(obj.Properties) == 0 {
  1029  		fmt.Fprintf(w, "        pass")
  1030  	}
  1031  	for _, prop := range obj.Properties {
  1032  		// Check that required arguments are present.  Also check that types are as expected.
  1033  		pname := PyName(prop.Name)
  1034  		ptype := mod.pyType(prop.Type)
  1035  		fmt.Fprintf(w, "        if %s and not isinstance(%s, %s):\n", pname, pname, ptype)
  1036  		fmt.Fprintf(w, "            raise TypeError(\"Expected argument '%s' to be a %s\")\n", pname, ptype)
  1037  
  1038  		if prop.DeprecationMessage != "" {
  1039  			escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
  1040  			fmt.Fprintf(w, "        if %s is not None:\n", pname)
  1041  			fmt.Fprintf(w, "            warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
  1042  			fmt.Fprintf(w, "            pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n\n", pname, escaped)
  1043  		}
  1044  
  1045  		// Now perform the assignment.
  1046  		fmt.Fprintf(w, "        pulumi.set(__self__, \"%[1]s\", %[1]s)\n", pname)
  1047  	}
  1048  	fmt.Fprintf(w, "\n")
  1049  
  1050  	// Write out Python property getters for each property.
  1051  	mod.genProperties(w, obj.Properties, false /*setters*/, "", func(prop *schema.Property) string {
  1052  		return mod.typeString(prop.Type, false /*input*/, false /*acceptMapping*/)
  1053  	})
  1054  
  1055  	// Produce an awaitable subclass.
  1056  	fmt.Fprint(w, "\n")
  1057  	fmt.Fprintf(w, "class %s(%s):\n", awaitableName, baseName)
  1058  
  1059  	// Emit __await__ and __iter__ in order to make this type awaitable.
  1060  	//
  1061  	// Note that we need __await__ to be an iterator, but we only want it to return one value. As such, we use
  1062  	// `if False: yield` to construct this.
  1063  	//
  1064  	// We also need the result of __await__ to be a plain, non-awaitable value. We achieve this by returning a new
  1065  	// instance of the base class.
  1066  	fmt.Fprintf(w, "    # pylint: disable=using-constant-test\n")
  1067  	fmt.Fprintf(w, "    def __await__(self):\n")
  1068  	fmt.Fprintf(w, "        if False:\n")
  1069  	fmt.Fprintf(w, "            yield self\n")
  1070  	fmt.Fprintf(w, "        return %s(", baseName)
  1071  	for i, prop := range obj.Properties {
  1072  		if i > 0 {
  1073  			fmt.Fprintf(w, ",")
  1074  		}
  1075  		pname := PyName(prop.Name)
  1076  		fmt.Fprintf(w, "\n            %s=self.%s", pname, pname)
  1077  	}
  1078  	fmt.Fprintf(w, ")\n")
  1079  
  1080  	return awaitableName
  1081  }
  1082  
  1083  func resourceName(res *schema.Resource) string {
  1084  	name := pyClassName(tokenToName(res.Token))
  1085  	if res.IsProvider {
  1086  		name = "Provider"
  1087  	}
  1088  	return name
  1089  }
  1090  
  1091  func (mod *modContext) genResource(res *schema.Resource) (string, error) {
  1092  	w := &bytes.Buffer{}
  1093  
  1094  	imports := imports{}
  1095  	mod.collectImportsForResource(res.Properties, imports, false /*input*/, res)
  1096  	mod.collectImportsForResource(res.InputProperties, imports, true /*input*/, res)
  1097  	if res.StateInputs != nil {
  1098  		mod.collectImportsForResource(res.StateInputs.Properties, imports, true /*input*/, res)
  1099  	}
  1100  	for _, method := range res.Methods {
  1101  		if method.Function.Inputs != nil {
  1102  			mod.collectImportsForResource(method.Function.Inputs.Properties, imports, true /*input*/, res)
  1103  		}
  1104  		if method.Function.Outputs != nil {
  1105  			mod.collectImportsForResource(method.Function.Outputs.Properties, imports, false /*input*/, res)
  1106  		}
  1107  	}
  1108  
  1109  	mod.genHeader(w, true /*needsSDK*/, imports)
  1110  
  1111  	name := resourceName(res)
  1112  
  1113  	resourceArgsName := fmt.Sprintf("%sArgs", name)
  1114  	// Some providers (e.g. Kubernetes) have types with the same name as resources (e.g. StorageClass in Kubernetes).
  1115  	// We've already shipped the input type (e.g. StorageClassArgs) in the same module as the resource, so we can't use
  1116  	// the same name for the resource's args class. When an input type exists that would conflict with the name of the
  1117  	// resource args class, we'll use a different name: `<Resource>InitArgs` instead of `<Resource>Args`.
  1118  	const alternateSuffix = "InitArgs"
  1119  	for _, t := range mod.types {
  1120  		if mod.details(t).inputType {
  1121  			if mod.unqualifiedObjectTypeName(t, true) == resourceArgsName {
  1122  				resourceArgsName = name + alternateSuffix
  1123  				break
  1124  			}
  1125  		}
  1126  	}
  1127  	// If we're using the alternate name, ensure the alternate name doesn't conflict with an input type.
  1128  	if strings.HasSuffix(resourceArgsName, alternateSuffix) {
  1129  		for _, t := range mod.types {
  1130  			if mod.details(t).inputType {
  1131  				if mod.unqualifiedObjectTypeName(t, true) == resourceArgsName {
  1132  					return "", fmt.Errorf("resource args class named %s in %s conflicts with input type", resourceArgsName, mod.mod)
  1133  				}
  1134  			}
  1135  		}
  1136  	}
  1137  
  1138  	// Export only the symbols we want exported.
  1139  	fmt.Fprintf(w, "__all__ = ['%s', '%s']\n\n", resourceArgsName, name)
  1140  
  1141  	// Produce an args class.
  1142  	argsComment := fmt.Sprintf("The set of arguments for constructing a %s resource.", name)
  1143  	err := mod.genType(w, resourceArgsName, argsComment, res.InputProperties, true, false)
  1144  	if err != nil {
  1145  		return "", err
  1146  	}
  1147  
  1148  	// Produce an unexported state class. It's currently only used internally inside the `get` method to opt-in to
  1149  	// the type/name metadata based translation behavior.
  1150  	// We can consider making use of it publicly in the future: removing the underscore prefix, exporting it from
  1151  	// `__all__`, and adding a static `get` overload that accepts it as an argument.
  1152  	hasStateInputs := !res.IsProvider && !res.IsComponent && res.StateInputs != nil &&
  1153  		len(res.StateInputs.Properties) > 0
  1154  	if hasStateInputs {
  1155  		stateComment := fmt.Sprintf("Input properties used for looking up and filtering %s resources.", name)
  1156  		err = mod.genType(w, fmt.Sprintf("_%sState", name), stateComment, res.StateInputs.Properties, true, false)
  1157  		if err != nil {
  1158  			return "", err
  1159  		}
  1160  	}
  1161  
  1162  	var baseType string
  1163  	switch {
  1164  	case res.IsProvider:
  1165  		baseType = "pulumi.ProviderResource"
  1166  	case res.IsComponent:
  1167  		baseType = "pulumi.ComponentResource"
  1168  	default:
  1169  		baseType = "pulumi.CustomResource"
  1170  	}
  1171  
  1172  	if !res.IsProvider && res.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
  1173  		escaped := strings.ReplaceAll(res.DeprecationMessage, `"`, `\"`)
  1174  		fmt.Fprintf(w, "warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n\n", escaped)
  1175  	}
  1176  
  1177  	// Produce a class definition with optional """ comment.
  1178  	fmt.Fprintf(w, "class %s(%s):\n", name, baseType)
  1179  	if res.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
  1180  		escaped := strings.ReplaceAll(res.DeprecationMessage, `"`, `\"`)
  1181  		fmt.Fprintf(w, "    warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n", escaped)
  1182  	}
  1183  
  1184  	// Determine if all inputs are optional.
  1185  	allOptionalInputs := true
  1186  	for _, prop := range res.InputProperties {
  1187  		allOptionalInputs = allOptionalInputs && !prop.IsRequired()
  1188  	}
  1189  
  1190  	// Emit __init__ overloads and implementation...
  1191  
  1192  	// Helper for generating an init method with inputs as function arguments.
  1193  	emitInitMethodSignature := func(methodName string) {
  1194  		fmt.Fprintf(w, "    def %s(__self__,\n", methodName)
  1195  		fmt.Fprintf(w, "                 resource_name: str,\n")
  1196  		fmt.Fprintf(w, "                 opts: Optional[pulumi.ResourceOptions] = None")
  1197  
  1198  		// If there's an argument type, emit it.
  1199  		for _, prop := range res.InputProperties {
  1200  			ty := mod.typeString(codegen.OptionalType(prop), true, true /*acceptMapping*/)
  1201  			fmt.Fprintf(w, ",\n                 %s: %s = None", InitParamName(prop.Name), ty)
  1202  		}
  1203  
  1204  		fmt.Fprintf(w, ",\n                 __props__=None):\n")
  1205  	}
  1206  
  1207  	// Emit an __init__ overload that accepts the resource's inputs as function arguments.
  1208  	fmt.Fprintf(w, "    @overload\n")
  1209  	emitInitMethodSignature("__init__")
  1210  	mod.genInitDocstring(w, res, resourceArgsName, false /*argsOverload*/)
  1211  	fmt.Fprintf(w, "        ...\n")
  1212  
  1213  	// Emit an __init__ overload that accepts the resource's inputs from the args class.
  1214  	fmt.Fprintf(w, "    @overload\n")
  1215  	fmt.Fprintf(w, "    def __init__(__self__,\n")
  1216  	fmt.Fprintf(w, "                 resource_name: str,\n")
  1217  	if allOptionalInputs {
  1218  		fmt.Fprintf(w, "                 args: Optional[%s] = None,\n", resourceArgsName)
  1219  	} else {
  1220  		fmt.Fprintf(w, "                 args: %s,\n", resourceArgsName)
  1221  	}
  1222  	fmt.Fprintf(w, "                 opts: Optional[pulumi.ResourceOptions] = None):\n")
  1223  	mod.genInitDocstring(w, res, resourceArgsName, true /*argsOverload*/)
  1224  	fmt.Fprintf(w, "        ...\n")
  1225  
  1226  	// Emit the actual implementation of __init__, which does the appropriate thing based on which
  1227  	// overload was called.
  1228  	fmt.Fprintf(w, "    def __init__(__self__, resource_name: str, *args, **kwargs):\n")
  1229  	fmt.Fprintf(w, "        resource_args, opts = _utilities.get_resource_args_opts(%s, pulumi.ResourceOptions, *args, **kwargs)\n", resourceArgsName)
  1230  	fmt.Fprintf(w, "        if resource_args is not None:\n")
  1231  	fmt.Fprintf(w, "            __self__._internal_init(resource_name, opts, **resource_args.__dict__)\n")
  1232  	fmt.Fprintf(w, "        else:\n")
  1233  	fmt.Fprintf(w, "            __self__._internal_init(resource_name, *args, **kwargs)\n")
  1234  	fmt.Fprintf(w, "\n")
  1235  
  1236  	// Emit the _internal_init helper method which provides the bulk of the __init__ implementation.
  1237  	emitInitMethodSignature("_internal_init")
  1238  	if res.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
  1239  		fmt.Fprintf(w, "        pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", name, res.DeprecationMessage)
  1240  	}
  1241  	fmt.Fprintf(w, "        opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts)\n")
  1242  	fmt.Fprintf(w, "        if not isinstance(opts, pulumi.ResourceOptions):\n")
  1243  	fmt.Fprintf(w, "            raise TypeError('Expected resource options to be a ResourceOptions instance')\n")
  1244  	if res.IsComponent {
  1245  		fmt.Fprintf(w, "        if opts.id is not None:\n")
  1246  		fmt.Fprintf(w, "            raise ValueError('ComponentResource classes do not support opts.id')\n")
  1247  		fmt.Fprintf(w, "        else:\n")
  1248  	} else {
  1249  		fmt.Fprintf(w, "        if opts.id is None:\n")
  1250  	}
  1251  	fmt.Fprintf(w, "            if __props__ is not None:\n")
  1252  	fmt.Fprintf(w, "                raise TypeError(")
  1253  	fmt.Fprintf(w, "'__props__ is only valid when passed in combination with a valid opts.id to get an existing resource')\n")
  1254  
  1255  	// We use an instance of the `<Resource>Args` class for `__props__` to opt-in to the type/name metadata based
  1256  	// translation behavior. The instance is created using `__new__` to avoid any validation in the `__init__` method,
  1257  	// values are set directly on its `__dict__`, including any additional output properties.
  1258  	fmt.Fprintf(w, "            __props__ = %[1]s.__new__(%[1]s)\n\n", resourceArgsName)
  1259  	fmt.Fprintf(w, "")
  1260  
  1261  	ins := codegen.NewStringSet()
  1262  	for _, prop := range res.InputProperties {
  1263  		pname := InitParamName(prop.Name)
  1264  		var arg interface{}
  1265  		var err error
  1266  
  1267  		// Fill in computed defaults for arguments.
  1268  		if prop.DefaultValue != nil {
  1269  			dv, err := getDefaultValue(prop.DefaultValue, codegen.UnwrapType(prop.Type))
  1270  			if err != nil {
  1271  				return "", err
  1272  			}
  1273  			fmt.Fprintf(w, "            if %s is None:\n", pname)
  1274  			fmt.Fprintf(w, "                %s = %s\n", pname, dv)
  1275  		}
  1276  
  1277  		// Check that required arguments are present.
  1278  		if prop.IsRequired() {
  1279  			fmt.Fprintf(w, "            if %s is None and not opts.urn:\n", pname)
  1280  			fmt.Fprintf(w, "                raise TypeError(\"Missing required property '%s'\")\n", pname)
  1281  		}
  1282  
  1283  		// Check that the property isn't deprecated
  1284  		if prop.DeprecationMessage != "" {
  1285  			escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
  1286  			fmt.Fprintf(w, "            if %s is not None and not opts.urn:\n", pname)
  1287  			fmt.Fprintf(w, "                warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
  1288  			fmt.Fprintf(w, "                pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", pname, escaped)
  1289  		}
  1290  
  1291  		// And add it to the dictionary.
  1292  		arg = pname
  1293  
  1294  		if prop.ConstValue != nil {
  1295  			arg, err = getConstValue(prop.ConstValue)
  1296  			if err != nil {
  1297  				return "", err
  1298  			}
  1299  		}
  1300  
  1301  		// If this resource is a provider then, regardless of the schema of the underlying provider
  1302  		// type, we must project all properties as strings. For all properties that are not strings,
  1303  		// we'll marshal them to JSON and use the JSON string as a string input.
  1304  		if res.IsProvider && !isStringType(prop.Type) {
  1305  			arg = fmt.Sprintf("pulumi.Output.from_input(%s).apply(pulumi.runtime.to_json) if %s is not None else None", arg, arg)
  1306  		}
  1307  		name := PyName(prop.Name)
  1308  		if prop.Secret {
  1309  			fmt.Fprintf(w, "            __props__.__dict__[%[1]q] = None if %[2]s is None else pulumi.Output.secret(%[2]s)\n", name, arg)
  1310  		} else {
  1311  			fmt.Fprintf(w, "            __props__.__dict__[%q] = %s\n", name, arg)
  1312  		}
  1313  
  1314  		ins.Add(prop.Name)
  1315  	}
  1316  
  1317  	var secretProps []string
  1318  	for _, prop := range res.Properties {
  1319  		// Default any pure output properties to None.  This ensures they are available as properties, even if
  1320  		// they don't ever get assigned a real value, and get documentation if available.
  1321  		if !ins.Has(prop.Name) {
  1322  			fmt.Fprintf(w, "            __props__.__dict__[%q] = None\n", PyName(prop.Name))
  1323  		}
  1324  
  1325  		if prop.Secret {
  1326  			secretProps = append(secretProps, prop.Name)
  1327  		}
  1328  	}
  1329  
  1330  	if len(res.Aliases) > 0 {
  1331  		fmt.Fprintf(w, `        alias_opts = pulumi.ResourceOptions(aliases=[`)
  1332  
  1333  		for i, alias := range res.Aliases {
  1334  			if i > 0 {
  1335  				fmt.Fprintf(w, ", ")
  1336  			}
  1337  			mod.writeAlias(w, alias)
  1338  		}
  1339  
  1340  		fmt.Fprintf(w, "])\n")
  1341  		fmt.Fprintf(w, "        opts = pulumi.ResourceOptions.merge(opts, alias_opts)\n")
  1342  	}
  1343  
  1344  	if len(secretProps) > 0 {
  1345  		fmt.Fprintf(w, `        secret_opts = pulumi.ResourceOptions(additional_secret_outputs=["%s"])`, strings.Join(secretProps, `", "`))
  1346  		fmt.Fprintf(w, "\n        opts = pulumi.ResourceOptions.merge(opts, secret_opts)\n")
  1347  	}
  1348  
  1349  	replaceOnChangesProps, errList := res.ReplaceOnChanges()
  1350  	for _, err := range errList {
  1351  		cmdutil.Diag().Warningf(&diag.Diag{Message: err.Error()})
  1352  	}
  1353  	if len(replaceOnChangesProps) > 0 {
  1354  		replaceOnChangesStrings := schema.PropertyListJoinToString(replaceOnChangesProps, PyName)
  1355  		fmt.Fprintf(w, `        replace_on_changes = pulumi.ResourceOptions(replace_on_changes=["%s"])`, strings.Join(replaceOnChangesStrings, `", "`))
  1356  		fmt.Fprintf(w, "\n        opts = pulumi.ResourceOptions.merge(opts, replace_on_changes)\n")
  1357  	}
  1358  
  1359  	// Finally, chain to the base constructor, which will actually register the resource.
  1360  	tok := res.Token
  1361  	if res.IsProvider {
  1362  		tok = mod.pkg.Name
  1363  	}
  1364  	fmt.Fprintf(w, "        super(%s, __self__).__init__(\n", name)
  1365  	fmt.Fprintf(w, "            '%s',\n", tok)
  1366  	fmt.Fprintf(w, "            resource_name,\n")
  1367  	fmt.Fprintf(w, "            __props__,\n")
  1368  	if res.IsComponent {
  1369  		fmt.Fprintf(w, "            opts,\n")
  1370  		fmt.Fprintf(w, "            remote=True)\n")
  1371  	} else {
  1372  		fmt.Fprintf(w, "            opts)\n")
  1373  	}
  1374  	fmt.Fprintf(w, "\n")
  1375  
  1376  	if !res.IsProvider && !res.IsComponent {
  1377  		fmt.Fprintf(w, "    @staticmethod\n")
  1378  		fmt.Fprintf(w, "    def get(resource_name: str,\n")
  1379  		fmt.Fprintf(w, "            id: pulumi.Input[str],\n")
  1380  		fmt.Fprintf(w, "            opts: Optional[pulumi.ResourceOptions] = None")
  1381  
  1382  		if hasStateInputs {
  1383  			for _, prop := range res.StateInputs.Properties {
  1384  				pname := InitParamName(prop.Name)
  1385  				ty := mod.typeString(codegen.OptionalType(prop), true, true /*acceptMapping*/)
  1386  				fmt.Fprintf(w, ",\n            %s: %s = None", pname, ty)
  1387  			}
  1388  		}
  1389  		fmt.Fprintf(w, ") -> '%s':\n", name)
  1390  		mod.genGetDocstring(w, res)
  1391  		fmt.Fprintf(w,
  1392  			"        opts = pulumi.ResourceOptions.merge(opts, pulumi.ResourceOptions(id=id))\n")
  1393  		fmt.Fprintf(w, "\n")
  1394  		if hasStateInputs {
  1395  			fmt.Fprintf(w, "        __props__ = _%[1]sState.__new__(_%[1]sState)\n\n", name)
  1396  		} else {
  1397  			// If we don't have any state inputs, we'll just instantiate the `<Resource>Args` class,
  1398  			// to opt-in to the improved translation behavior.
  1399  			fmt.Fprintf(w, "        __props__ = %[1]s.__new__(%[1]s)\n\n", resourceArgsName)
  1400  		}
  1401  
  1402  		stateInputs := codegen.NewStringSet()
  1403  		if res.StateInputs != nil {
  1404  			for _, prop := range res.StateInputs.Properties {
  1405  				stateInputs.Add(prop.Name)
  1406  				fmt.Fprintf(w, "        __props__.__dict__[%q] = %s\n", PyName(prop.Name), InitParamName(prop.Name))
  1407  			}
  1408  		}
  1409  		for _, prop := range res.Properties {
  1410  			if !stateInputs.Has(prop.Name) {
  1411  				fmt.Fprintf(w, "        __props__.__dict__[%q] = None\n", PyName(prop.Name))
  1412  			}
  1413  		}
  1414  
  1415  		fmt.Fprintf(w, "        return %s(resource_name, opts=opts, __props__=__props__)\n\n", name)
  1416  	}
  1417  
  1418  	// Write out Python property getters for each of the resource's properties.
  1419  	mod.genProperties(w, res.Properties, false /*setters*/, "", func(prop *schema.Property) string {
  1420  		ty := mod.typeString(prop.Type, false /*input*/, false /*acceptMapping*/)
  1421  		return fmt.Sprintf("pulumi.Output[%s]", ty)
  1422  	})
  1423  
  1424  	// Write out methods.
  1425  	mod.genMethods(w, res)
  1426  
  1427  	return w.String(), nil
  1428  }
  1429  
  1430  func (mod *modContext) genProperties(w io.Writer, properties []*schema.Property, setters bool, indent string,
  1431  	propType func(prop *schema.Property) string) {
  1432  	// Write out Python properties for each property. If there is a property named "property", it will
  1433  	// be emitted last to avoid conflicting with the built-in `@property` decorator function. We do
  1434  	// this instead of importing `builtins` and fully qualifying the decorator as `@builtins.property`
  1435  	// because that wouldn't address the problem if there was a property named "builtins".
  1436  	emitProp := func(pname string, prop *schema.Property) {
  1437  		ty := propType(prop)
  1438  		fmt.Fprintf(w, "%s    @property\n", indent)
  1439  		if pname == prop.Name {
  1440  			fmt.Fprintf(w, "%s    @pulumi.getter\n", indent)
  1441  		} else {
  1442  			fmt.Fprintf(w, "%s    @pulumi.getter(name=%q)\n", indent, prop.Name)
  1443  		}
  1444  		fmt.Fprintf(w, "%s    def %s(self) -> %s:\n", indent, pname, ty)
  1445  		if prop.Comment != "" {
  1446  			printComment(w, prop.Comment, indent+"        ")
  1447  		}
  1448  		fmt.Fprintf(w, "%s        return pulumi.get(self, %q)\n\n", indent, pname)
  1449  
  1450  		if setters {
  1451  			fmt.Fprintf(w, "%s    @%s.setter\n", indent, pname)
  1452  			fmt.Fprintf(w, "%s    def %s(self, value: %s):\n", indent, pname, ty)
  1453  			fmt.Fprintf(w, "%s        pulumi.set(self, %q, value)\n\n", indent, pname)
  1454  		}
  1455  	}
  1456  	var propNamedProperty *schema.Property
  1457  	for _, prop := range properties {
  1458  		pname := PyName(prop.Name)
  1459  		// If there is a property named "property", skip it, and emit it last.
  1460  		if pname == "property" {
  1461  			propNamedProperty = prop
  1462  			continue
  1463  		}
  1464  		emitProp(pname, prop)
  1465  	}
  1466  	if propNamedProperty != nil {
  1467  		emitProp("property", propNamedProperty)
  1468  	}
  1469  }
  1470  
  1471  func (mod *modContext) genMethods(w io.Writer, res *schema.Resource) {
  1472  	genReturnType := func(method *schema.Method) string {
  1473  		obj := method.Function.Outputs
  1474  		name := pyClassName(title(method.Name)) + "Result"
  1475  
  1476  		// Produce a class definition with optional """ comment.
  1477  		fmt.Fprintf(w, "    @pulumi.output_type\n")
  1478  		fmt.Fprintf(w, "    class %s:\n", name)
  1479  		printComment(w, obj.Comment, "        ")
  1480  
  1481  		// Now generate an initializer with properties for all inputs.
  1482  		fmt.Fprintf(w, "        def __init__(__self__")
  1483  		for _, prop := range obj.Properties {
  1484  			fmt.Fprintf(w, ", %s=None", PyName(prop.Name))
  1485  		}
  1486  		fmt.Fprintf(w, "):\n")
  1487  		for _, prop := range obj.Properties {
  1488  			// Check that required arguments are present.  Also check that types are as expected.
  1489  			pname := PyName(prop.Name)
  1490  			ptype := mod.pyType(prop.Type)
  1491  			fmt.Fprintf(w, "            if %s and not isinstance(%s, %s):\n", pname, pname, ptype)
  1492  			fmt.Fprintf(w, "                raise TypeError(\"Expected argument '%s' to be a %s\")\n", pname, ptype)
  1493  
  1494  			if prop.DeprecationMessage != "" {
  1495  				escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
  1496  				fmt.Fprintf(w, "            if %s is not None:\n", pname)
  1497  				fmt.Fprintf(w, "                warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
  1498  				fmt.Fprintf(w, "                pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n\n", pname, escaped)
  1499  			}
  1500  
  1501  			// Now perform the assignment.
  1502  			fmt.Fprintf(w, "            pulumi.set(__self__, \"%[1]s\", %[1]s)\n", pname)
  1503  		}
  1504  		fmt.Fprintf(w, "\n")
  1505  
  1506  		// Write out Python property getters for each property.
  1507  		mod.genProperties(w, obj.Properties, false /*setters*/, "    ", func(prop *schema.Property) string {
  1508  			return mod.typeString(prop.Type, false /*input*/, false /*acceptMapping*/)
  1509  		})
  1510  
  1511  		return name
  1512  	}
  1513  
  1514  	genMethod := func(method *schema.Method) {
  1515  		methodName := PyName(method.Name)
  1516  		fun := method.Function
  1517  
  1518  		shouldLiftReturn := mod.liftSingleValueMethodReturns && method.Function.Outputs != nil && len(method.Function.Outputs.Properties) == 1
  1519  
  1520  		// If there is a return type, emit it.
  1521  		var retTypeName, retTypeNameQualified, retTypeNameQualifiedOutput, methodRetType string
  1522  		if fun.Outputs != nil {
  1523  			retTypeName = genReturnType(method)
  1524  			retTypeNameQualified = fmt.Sprintf("%s.%s", resourceName(res), retTypeName)
  1525  			retTypeNameQualifiedOutput = fmt.Sprintf("pulumi.Output['%s']", retTypeNameQualified)
  1526  
  1527  			if shouldLiftReturn {
  1528  				methodRetType = fmt.Sprintf("pulumi.Output['%s']", mod.pyType(fun.Outputs.Properties[0].Type))
  1529  			} else {
  1530  				methodRetType = retTypeNameQualifiedOutput
  1531  			}
  1532  		}
  1533  
  1534  		var args []*schema.Property
  1535  		if fun.Inputs != nil {
  1536  			// Filter out the __self__ argument from the inputs.
  1537  			args = make([]*schema.Property, 0, len(fun.Inputs.InputShape.Properties)-1)
  1538  			for _, arg := range fun.Inputs.InputShape.Properties {
  1539  				if arg.Name == "__self__" {
  1540  					continue
  1541  				}
  1542  				args = append(args, arg)
  1543  			}
  1544  			// Sort required args first.
  1545  			sort.Slice(args, func(i, j int) bool {
  1546  				pi, pj := args[i], args[j]
  1547  				switch {
  1548  				case pi.IsRequired() != pj.IsRequired():
  1549  					return pi.IsRequired() && !pj.IsRequired()
  1550  				default:
  1551  					return pi.Name < pj.Name
  1552  				}
  1553  			})
  1554  		}
  1555  
  1556  		// Write out the function signature.
  1557  		def := fmt.Sprintf("    def %s(", methodName)
  1558  		var indent string
  1559  		if len(args) > 0 {
  1560  			indent = strings.Repeat(" ", len(def))
  1561  		}
  1562  		fmt.Fprintf(w, "%s__self__", def)
  1563  		// Bare `*` argument to force callers to use named arguments.
  1564  		if len(args) > 0 {
  1565  			fmt.Fprintf(w, ", *")
  1566  		}
  1567  		for _, arg := range args {
  1568  			pname := PyName(arg.Name)
  1569  			ty := mod.typeString(arg.Type, true, false /*acceptMapping*/)
  1570  			var defaultValue string
  1571  			if !arg.IsRequired() {
  1572  				defaultValue = " = None"
  1573  			}
  1574  			fmt.Fprintf(w, ",\n%s%s: %s%s", indent, pname, ty, defaultValue)
  1575  		}
  1576  		if retTypeNameQualifiedOutput != "" {
  1577  			fmt.Fprintf(w, ") -> %s:\n", methodRetType)
  1578  		} else {
  1579  			fmt.Fprintf(w, ") -> None:\n")
  1580  		}
  1581  
  1582  		// If this func has documentation, write it at the top of the docstring, otherwise use a generic comment.
  1583  		docs := &bytes.Buffer{}
  1584  		if fun.Comment != "" {
  1585  			fmt.Fprintln(docs, codegen.FilterExamples(fun.Comment, "python"))
  1586  		}
  1587  		if len(args) > 0 {
  1588  			fmt.Fprintln(docs, "")
  1589  			for _, arg := range args {
  1590  				mod.genPropDocstring(docs, PyName(arg.Name), arg, false /*acceptMapping*/)
  1591  			}
  1592  		}
  1593  		printComment(w, docs.String(), "        ")
  1594  
  1595  		if fun.DeprecationMessage != "" {
  1596  			fmt.Fprintf(w, "        pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", methodName,
  1597  				fun.DeprecationMessage)
  1598  		}
  1599  
  1600  		// Copy the function arguments into a dictionary.
  1601  		fmt.Fprintf(w, "        __args__ = dict()\n")
  1602  		fmt.Fprintf(w, "        __args__['__self__'] = __self__\n")
  1603  		for _, arg := range args {
  1604  			pname := PyName(arg.Name)
  1605  			fmt.Fprintf(w, "        __args__['%s'] = %s\n", arg.Name, pname)
  1606  		}
  1607  
  1608  		// Now simply call the function with the arguments.
  1609  		var typ string
  1610  		if retTypeNameQualified != "" {
  1611  			// Pass along the private output_type we generated, so any nested output classes are instantiated by
  1612  			// the call.
  1613  			typ = fmt.Sprintf(", typ=%s", retTypeNameQualified)
  1614  		}
  1615  
  1616  		if method.Function.Outputs == nil {
  1617  			fmt.Fprintf(w, "        pulumi.runtime.call('%s', __args__, res=__self__%s)\n", fun.Token, typ)
  1618  		} else if shouldLiftReturn {
  1619  			// Store the return in a variable and return the property output
  1620  			fmt.Fprintf(w, "        __result__ = pulumi.runtime.call('%s', __args__, res=__self__%s)\n", fun.Token, typ)
  1621  			fmt.Fprintf(w, "        return __result__.%s\n", PyName(fun.Outputs.Properties[0].Name))
  1622  		} else {
  1623  			// Otherwise return the call directly
  1624  			fmt.Fprintf(w, "        return pulumi.runtime.call('%s', __args__, res=__self__%s)\n", fun.Token, typ)
  1625  		}
  1626  
  1627  		fmt.Fprintf(w, "\n")
  1628  	}
  1629  
  1630  	for _, method := range res.Methods {
  1631  		genMethod(method)
  1632  	}
  1633  }
  1634  
  1635  func (mod *modContext) writeAlias(w io.Writer, alias *schema.Alias) {
  1636  	fmt.Fprint(w, "pulumi.Alias(")
  1637  	parts := []string{}
  1638  	if alias.Name != nil {
  1639  		parts = append(parts, fmt.Sprintf("name=\"%v\"", *alias.Name))
  1640  	}
  1641  	if alias.Project != nil {
  1642  		parts = append(parts, fmt.Sprintf("project=\"%v\"", *alias.Project))
  1643  	}
  1644  	if alias.Type != nil {
  1645  		parts = append(parts, fmt.Sprintf("type_=\"%v\"", *alias.Type))
  1646  	}
  1647  
  1648  	for i, part := range parts {
  1649  		if i > 0 {
  1650  			fmt.Fprint(w, ", ")
  1651  		}
  1652  		fmt.Fprint(w, part)
  1653  	}
  1654  	fmt.Fprint(w, ")")
  1655  }
  1656  
  1657  func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
  1658  	w := &bytes.Buffer{}
  1659  
  1660  	imports := imports{}
  1661  	if fun.Inputs != nil {
  1662  		mod.collectImports(fun.Inputs.Properties, imports, true)
  1663  	}
  1664  	if fun.Outputs != nil {
  1665  		mod.collectImports(fun.Outputs.Properties, imports, false)
  1666  	}
  1667  
  1668  	mod.genHeader(w, true /*needsSDK*/, imports)
  1669  
  1670  	var baseName, awaitableName string
  1671  	if fun.Outputs != nil {
  1672  		baseName, awaitableName = awaitableTypeNames(fun.Outputs.Token)
  1673  	}
  1674  	name := PyName(tokenToName(fun.Token))
  1675  
  1676  	// Export only the symbols we want exported.
  1677  	fmt.Fprintf(w, "__all__ = [\n")
  1678  	if fun.Outputs != nil {
  1679  		fmt.Fprintf(w, "    '%s',\n", baseName)
  1680  		fmt.Fprintf(w, "    '%s',\n", awaitableName)
  1681  	}
  1682  	fmt.Fprintf(w, "    '%s',\n", name)
  1683  	if fun.NeedsOutputVersion() {
  1684  		fmt.Fprintf(w, "    '%s_output',\n", name)
  1685  	}
  1686  	fmt.Fprintf(w, "]\n\n")
  1687  
  1688  	if fun.DeprecationMessage != "" {
  1689  		escaped := strings.ReplaceAll(fun.DeprecationMessage, `"`, `\"`)
  1690  		fmt.Fprintf(w, "warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n", escaped)
  1691  	}
  1692  
  1693  	// If there is a return type, emit it.
  1694  	retTypeName := ""
  1695  	var rets []*schema.Property
  1696  	if fun.Outputs != nil {
  1697  		retTypeName, rets = mod.genAwaitableType(w, fun.Outputs), fun.Outputs.Properties
  1698  		fmt.Fprintf(w, "\n\n")
  1699  	}
  1700  
  1701  	var args []*schema.Property
  1702  	if fun.Inputs != nil {
  1703  		args = fun.Inputs.Properties
  1704  	}
  1705  
  1706  	mod.genFunDef(w, name, retTypeName, args, false /* wrapInput */)
  1707  	mod.genFunDocstring(w, fun)
  1708  	mod.genFunDeprecationMessage(w, fun)
  1709  
  1710  	// Copy the function arguments into a dictionary.
  1711  	fmt.Fprintf(w, "    __args__ = dict()\n")
  1712  	for _, arg := range args {
  1713  		// TODO: args validation.
  1714  		fmt.Fprintf(w, "    __args__['%s'] = %s\n", arg.Name, PyName(arg.Name))
  1715  	}
  1716  
  1717  	// If the caller explicitly specified a version, use it, otherwise inject this package's version.
  1718  	fmt.Fprintf(w, "    opts = pulumi.InvokeOptions.merge(_utilities.get_invoke_opts_defaults(), opts)\n")
  1719  
  1720  	// Now simply invoke the runtime function with the arguments.
  1721  	var typ string
  1722  	if fun.Outputs != nil {
  1723  		// Pass along the private output_type we generated, so any nested outputs classes are instantiated by
  1724  		// the call to invoke.
  1725  		typ = fmt.Sprintf(", typ=%s", baseName)
  1726  	}
  1727  	fmt.Fprintf(w, "    __ret__ = pulumi.runtime.invoke('%s', __args__, opts=opts%s).value\n", fun.Token, typ)
  1728  	fmt.Fprintf(w, "\n")
  1729  
  1730  	// And copy the results to an object, if there are indeed any expected returns.
  1731  	if fun.Outputs != nil {
  1732  		fmt.Fprintf(w, "    return %s(", retTypeName)
  1733  		for i, ret := range rets {
  1734  			if i > 0 {
  1735  				fmt.Fprintf(w, ",")
  1736  			}
  1737  			// Use the get_dict_value utility instead of calling __ret__.get directly in case the __ret__
  1738  			// object has a get property that masks the underlying dict subclass's get method.
  1739  			fmt.Fprintf(w, "\n        %[1]s=__ret__.%[1]s", PyName(ret.Name))
  1740  		}
  1741  		fmt.Fprintf(w, ")\n")
  1742  	}
  1743  
  1744  	mod.genFunctionOutputVersion(w, fun)
  1745  	return w.String(), nil
  1746  }
  1747  
  1748  func (mod *modContext) genFunDocstring(w io.Writer, fun *schema.Function) {
  1749  	var args []*schema.Property
  1750  	if fun.Inputs != nil {
  1751  		args = fun.Inputs.Properties
  1752  	}
  1753  
  1754  	// If this func has documentation, write it at the top of the docstring, otherwise use a generic comment.
  1755  	docs := &bytes.Buffer{}
  1756  	if fun.Comment != "" {
  1757  		fmt.Fprintln(docs, codegen.FilterExamples(fun.Comment, "python"))
  1758  	} else {
  1759  		fmt.Fprintln(docs, "Use this data source to access information about an existing resource.")
  1760  	}
  1761  	if len(args) > 0 {
  1762  		fmt.Fprintln(docs, "")
  1763  		for _, arg := range args {
  1764  			mod.genPropDocstring(docs, PyName(arg.Name), arg, true /*acceptMapping*/)
  1765  		}
  1766  	}
  1767  	printComment(w, docs.String(), "    ")
  1768  }
  1769  
  1770  func (mod *modContext) genFunDeprecationMessage(w io.Writer, fun *schema.Function) {
  1771  	if fun.DeprecationMessage == "" {
  1772  		return
  1773  	}
  1774  	name := PyName(tokenToName(fun.Token))
  1775  	fmt.Fprintf(w, "    pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", name, fun.DeprecationMessage)
  1776  }
  1777  
  1778  // Generates the function signature line `def fn(...):` without the body.
  1779  func (mod *modContext) genFunDef(w io.Writer, name, retTypeName string, args []*schema.Property, wrapInput bool) {
  1780  	def := fmt.Sprintf("def %s(", name)
  1781  	var indent string
  1782  	if len(args) > 0 {
  1783  		indent = strings.Repeat(" ", len(def))
  1784  	}
  1785  	fmt.Fprintf(w, def)
  1786  	for i, arg := range args {
  1787  		var ind string
  1788  		if i != 0 {
  1789  			ind = indent
  1790  		}
  1791  		pname := PyName(arg.Name)
  1792  
  1793  		var argType schema.Type
  1794  		if wrapInput {
  1795  			argType = &schema.OptionalType{
  1796  				ElementType: &schema.InputType{
  1797  					ElementType: arg.Type,
  1798  				},
  1799  			}
  1800  		} else {
  1801  			argType = codegen.OptionalType(arg)
  1802  		}
  1803  
  1804  		ty := mod.typeString(argType, true /*input*/, true /*acceptMapping*/)
  1805  		fmt.Fprintf(w, "%s%s: %s = None,\n", ind, pname, ty)
  1806  	}
  1807  	fmt.Fprintf(w, "%sopts: Optional[pulumi.InvokeOptions] = None", indent)
  1808  	if retTypeName != "" {
  1809  		fmt.Fprintf(w, ") -> %s:\n", retTypeName)
  1810  	} else {
  1811  		fmt.Fprintf(w, "):\n")
  1812  	}
  1813  }
  1814  
  1815  // Generates `def ${fn}_output(..) version lifted to work on
  1816  // `Input`-wrapped arguments and producing an `Output`-wrapped result.
  1817  func (mod *modContext) genFunctionOutputVersion(w io.Writer, fun *schema.Function) {
  1818  	if !fun.NeedsOutputVersion() {
  1819  		return
  1820  	}
  1821  
  1822  	var retTypeName string
  1823  	if fun.Outputs != nil {
  1824  		originalOutputTypeName, _ := awaitableTypeNames(fun.Outputs.Token)
  1825  		retTypeName = fmt.Sprintf("pulumi.Output[%s]", originalOutputTypeName)
  1826  	} else {
  1827  		retTypeName = "pulumi.Output[void]"
  1828  	}
  1829  
  1830  	originalName := PyName(tokenToName(fun.Token))
  1831  	outputSuffixedName := fmt.Sprintf("%s_output", originalName)
  1832  
  1833  	var args []*schema.Property
  1834  	if fun.Inputs != nil {
  1835  		args = fun.Inputs.Properties
  1836  	}
  1837  
  1838  	fmt.Fprintf(w, "\n\n@_utilities.lift_output_func(%s)\n", originalName)
  1839  	mod.genFunDef(w, outputSuffixedName, retTypeName, args, true /*wrapInput*/)
  1840  	mod.genFunDocstring(w, fun)
  1841  	mod.genFunDeprecationMessage(w, fun)
  1842  	fmt.Fprintf(w, "    ...\n")
  1843  }
  1844  
  1845  func (mod *modContext) genEnums(w io.Writer, enums []*schema.EnumType) error {
  1846  	// Header
  1847  	mod.genHeader(w, false /*needsSDK*/, nil)
  1848  
  1849  	// Enum import
  1850  	fmt.Fprintf(w, "from enum import Enum\n\n")
  1851  
  1852  	// Export only the symbols we want exported.
  1853  	fmt.Fprintf(w, "__all__ = [\n")
  1854  	for _, enum := range enums {
  1855  		fmt.Fprintf(w, "    '%s',\n", tokenToName(enum.Token))
  1856  
  1857  	}
  1858  	fmt.Fprintf(w, "]\n\n\n")
  1859  
  1860  	for i, enum := range enums {
  1861  		if err := mod.genEnum(w, enum); err != nil {
  1862  			return err
  1863  		}
  1864  		if i != len(enums)-1 {
  1865  			fmt.Fprintf(w, "\n\n")
  1866  		}
  1867  	}
  1868  	return nil
  1869  }
  1870  
  1871  func (mod *modContext) genEnum(w io.Writer, enum *schema.EnumType) error {
  1872  	indent := "    "
  1873  	enumName := tokenToName(enum.Token)
  1874  	underlyingType := mod.typeString(enum.ElementType, false, false)
  1875  
  1876  	switch enum.ElementType {
  1877  	case schema.StringType, schema.IntType, schema.NumberType:
  1878  		fmt.Fprintf(w, "class %s(%s, Enum):\n", enumName, underlyingType)
  1879  		printComment(w, enum.Comment, indent)
  1880  		for _, e := range enum.Elements {
  1881  			// If the enum doesn't have a name, set the value as the name.
  1882  			if e.Name == "" {
  1883  				e.Name = fmt.Sprintf("%v", e.Value)
  1884  			}
  1885  
  1886  			name, err := makeSafeEnumName(e.Name, enumName)
  1887  			if err != nil {
  1888  				return err
  1889  			}
  1890  			e.Name = name
  1891  
  1892  			fmt.Fprintf(w, "%s%s = ", indent, e.Name)
  1893  			if val, ok := e.Value.(string); ok {
  1894  				fmt.Fprintf(w, "%q\n", val)
  1895  			} else {
  1896  				fmt.Fprintf(w, "%v\n", e.Value)
  1897  			}
  1898  			if e.Comment != "" {
  1899  				printComment(w, e.Comment, indent)
  1900  			}
  1901  		}
  1902  	default:
  1903  		return fmt.Errorf("enums of type %s are not yet implemented for this language", enum.ElementType.String())
  1904  	}
  1905  
  1906  	return nil
  1907  }
  1908  
  1909  func visitObjectTypes(properties []*schema.Property, visitor func(objectOrResource schema.Type)) {
  1910  	codegen.VisitTypeClosure(properties, func(t schema.Type) {
  1911  		switch st := t.(type) {
  1912  		case *schema.EnumType, *schema.ObjectType, *schema.ResourceType:
  1913  			visitor(st)
  1914  		}
  1915  	})
  1916  }
  1917  
  1918  func (mod *modContext) collectImports(properties []*schema.Property, imports imports, input bool) {
  1919  	mod.collectImportsForResource(properties, imports, input, nil)
  1920  }
  1921  
  1922  func (mod *modContext) collectImportsForResource(properties []*schema.Property, imports imports, input bool,
  1923  	res *schema.Resource) {
  1924  	codegen.VisitTypeClosure(properties, func(t schema.Type) {
  1925  		switch t := t.(type) {
  1926  		case *schema.ObjectType:
  1927  			imports.addType(mod, t, input)
  1928  		case *schema.EnumType:
  1929  			imports.addEnum(mod, t)
  1930  		case *schema.ResourceType:
  1931  			// Don't import itself.
  1932  			if t.Resource != res {
  1933  				imports.addResource(mod, t)
  1934  			}
  1935  		}
  1936  	})
  1937  }
  1938  
  1939  var requirementRegex = regexp.MustCompile(`^>=([^,]+),<[^,]+$`)
  1940  var pep440AlphaRegex = regexp.MustCompile(`^(\d+\.\d+\.\d)+a(\d+)$`)
  1941  var pep440BetaRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)b(\d+)$`)
  1942  var pep440RCRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)rc(\d+)$`)
  1943  var pep440DevRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)\.dev(\d+)$`)
  1944  
  1945  var oldestAllowedPulumi = semver.Version{
  1946  	Major: 0,
  1947  	Minor: 17,
  1948  	Patch: 28,
  1949  }
  1950  
  1951  func sanitizePackageDescription(description string) string {
  1952  	lines := strings.SplitN(description, "\n", 2)
  1953  	if len(lines) > 0 {
  1954  		return lines[0]
  1955  	}
  1956  	return ""
  1957  }
  1958  
  1959  func genPulumiPluginFile(pkg *schema.Package) ([]byte, error) {
  1960  	plugin := &plugin.PulumiPluginJSON{
  1961  		Resource: true,
  1962  		Name:     pkg.Name,
  1963  		Server:   pkg.PluginDownloadURL,
  1964  	}
  1965  
  1966  	if info, ok := pkg.Language["python"].(PackageInfo); pkg.Version != nil && ok && info.RespectSchemaVersion {
  1967  		plugin.Version = pkg.Version.String()
  1968  	}
  1969  
  1970  	return plugin.JSON()
  1971  }
  1972  
  1973  // genPackageMetadata generates all the non-code metadata required by a Pulumi package.
  1974  func genPackageMetadata(
  1975  	tool string, pkg *schema.Package, pyPkgName string, requires map[string]string, pythonRequires string) (string, error) {
  1976  
  1977  	w := &bytes.Buffer{}
  1978  	(&modContext{tool: tool}).genHeader(w, false /*needsSDK*/, nil)
  1979  
  1980  	// Now create a standard Python package from the metadata.
  1981  	fmt.Fprintf(w, "import errno\n")
  1982  	fmt.Fprintf(w, "from setuptools import setup, find_packages\n")
  1983  	fmt.Fprintf(w, "from setuptools.command.install import install\n")
  1984  	fmt.Fprintf(w, "from subprocess import check_call\n")
  1985  	fmt.Fprintf(w, "\n\n")
  1986  
  1987  	// Create a constant for the version number to replace during build
  1988  	version := "0.0.0"
  1989  	pluginVersion := version
  1990  	info, ok := pkg.Language["python"].(PackageInfo)
  1991  	if pkg.Version != nil && ok && info.RespectSchemaVersion {
  1992  		version = pypiVersion(*pkg.Version)
  1993  		pluginVersion = pkg.Version.String()
  1994  	}
  1995  	fmt.Fprintf(w, "VERSION = \"%s\"\n", version)
  1996  	fmt.Fprintf(w, "PLUGIN_VERSION = \"%s\"\n\n", pluginVersion)
  1997  
  1998  	// Create a command that will install the Pulumi plugin for this resource provider.
  1999  	fmt.Fprintf(w, "class InstallPluginCommand(install):\n")
  2000  	fmt.Fprintf(w, "    def run(self):\n")
  2001  	fmt.Fprintf(w, "        install.run(self)\n")
  2002  	fmt.Fprintf(w, "        try:\n")
  2003  	if pkg.PluginDownloadURL == "" {
  2004  		fmt.Fprintf(w, "            check_call(['pulumi', 'plugin', 'install', 'resource', '%s', PLUGIN_VERSION])\n", pkg.Name)
  2005  	} else {
  2006  		fmt.Fprintf(w, "            check_call(['pulumi', 'plugin', 'install', 'resource', '%s', PLUGIN_VERSION, '--server', '%s'])\n", pkg.Name, pkg.PluginDownloadURL)
  2007  	}
  2008  	fmt.Fprintf(w, "        except OSError as error:\n")
  2009  	fmt.Fprintf(w, "            if error.errno == errno.ENOENT:\n")
  2010  	fmt.Fprintf(w, "                print(f\"\"\"\n")
  2011  	fmt.Fprintf(w, "                There was an error installing the %s resource provider plugin.\n", pkg.Name)
  2012  	fmt.Fprintf(w, "                It looks like `pulumi` is not installed on your system.\n")
  2013  	fmt.Fprintf(w, "                Please visit https://pulumi.com/ to install the Pulumi CLI.\n")
  2014  	fmt.Fprintf(w, "                You may try manually installing the plugin by running\n")
  2015  	fmt.Fprintf(w, "                `pulumi plugin install resource %s {PLUGIN_VERSION}`\n", pkg.Name)
  2016  	fmt.Fprintf(w, "                \"\"\")\n")
  2017  	fmt.Fprintf(w, "            else:\n")
  2018  	fmt.Fprintf(w, "                raise\n")
  2019  	fmt.Fprintf(w, "\n\n")
  2020  
  2021  	// Generate a readme method which will load README.rst, we use this to fill out the
  2022  	// long_description field in the setup call.
  2023  	fmt.Fprintf(w, "def readme():\n")
  2024  	fmt.Fprintf(w, "    try:\n")
  2025  	fmt.Fprintf(w, "        with open('README.md', encoding='utf-8') as f:\n")
  2026  	fmt.Fprintf(w, "            return f.read()\n")
  2027  	fmt.Fprintf(w, "    except FileNotFoundError:\n")
  2028  	fmt.Fprintf(w, "        return \"%s Pulumi Package - Development Version\"\n", pkg.Name)
  2029  	fmt.Fprintf(w, "\n\n")
  2030  
  2031  	// Finally, the actual setup part.
  2032  	fmt.Fprintf(w, "setup(name='%s',\n", pyPkgName)
  2033  	if pythonRequires != "" {
  2034  		fmt.Fprintf(w, "      python_requires='%s',\n", pythonRequires)
  2035  	}
  2036  	fmt.Fprintf(w, "      version=VERSION,\n")
  2037  	if pkg.Description != "" {
  2038  		fmt.Fprintf(w, "      description=%q,\n", sanitizePackageDescription(pkg.Description))
  2039  	}
  2040  	fmt.Fprintf(w, "      long_description=readme(),\n")
  2041  	fmt.Fprintf(w, "      long_description_content_type='text/markdown',\n")
  2042  	fmt.Fprintf(w, "      cmdclass={\n")
  2043  	fmt.Fprintf(w, "          'install': InstallPluginCommand,\n")
  2044  	fmt.Fprintf(w, "      },\n")
  2045  	if pkg.Keywords != nil {
  2046  		fmt.Fprintf(w, "      keywords='")
  2047  		for i, kw := range pkg.Keywords {
  2048  			if i > 0 {
  2049  				fmt.Fprint(w, " ")
  2050  			}
  2051  			fmt.Fprint(w, kw)
  2052  		}
  2053  		fmt.Fprintf(w, "',\n")
  2054  	}
  2055  	if pkg.Homepage != "" {
  2056  		fmt.Fprintf(w, "      url='%s',\n", pkg.Homepage)
  2057  	}
  2058  	if pkg.Repository != "" {
  2059  		fmt.Fprintf(w, "      project_urls={\n")
  2060  		fmt.Fprintf(w, "          'Repository': '%s'\n", pkg.Repository)
  2061  		fmt.Fprintf(w, "      },\n")
  2062  	}
  2063  	if pkg.License != "" {
  2064  		fmt.Fprintf(w, "      license='%s',\n", pkg.License)
  2065  	}
  2066  	fmt.Fprintf(w, "      packages=find_packages(),\n")
  2067  
  2068  	// Publish type metadata: PEP 561
  2069  	fmt.Fprintf(w, "      package_data={\n")
  2070  	fmt.Fprintf(w, "          '%s': [\n", pyPkgName)
  2071  	fmt.Fprintf(w, "              'py.typed',\n")
  2072  	fmt.Fprintf(w, "              'pulumi-plugin.json',\n")
  2073  
  2074  	fmt.Fprintf(w, "          ]\n")
  2075  	fmt.Fprintf(w, "      },\n")
  2076  
  2077  	// Ensure that the Pulumi SDK has an entry if not specified. If the SDK _is_ specified, ensure
  2078  	// that it specifies an acceptable version range.
  2079  	if pulumiReq, ok := requires["pulumi"]; ok {
  2080  		// We expect a specific pattern of ">=version,<version" here.
  2081  		matches := requirementRegex.FindStringSubmatch(pulumiReq)
  2082  		if len(matches) != 2 {
  2083  			return "", fmt.Errorf("invalid requirement specifier \"%s\"; expected \">=version1,<version2\"", pulumiReq)
  2084  		}
  2085  
  2086  		lowerBound, err := pep440VersionToSemver(matches[1])
  2087  		if err != nil {
  2088  			return "", fmt.Errorf("invalid version for lower bound: %v", err)
  2089  		}
  2090  		if lowerBound.LT(oldestAllowedPulumi) {
  2091  			return "", fmt.Errorf("lower version bound must be at least %v", oldestAllowedPulumi)
  2092  		}
  2093  	} else {
  2094  		if requires == nil {
  2095  			requires = map[string]string{}
  2096  		}
  2097  		requires["pulumi"] = ""
  2098  	}
  2099  
  2100  	// Sort the entries so they are deterministic.
  2101  	reqNames := []string{
  2102  		"semver>=2.8.1",
  2103  		"parver>=0.2.1",
  2104  	}
  2105  	for req := range requires {
  2106  		reqNames = append(reqNames, req)
  2107  	}
  2108  	sort.Strings(reqNames)
  2109  
  2110  	fmt.Fprintf(w, "      install_requires=[\n")
  2111  	for i, req := range reqNames {
  2112  		var comma string
  2113  		if i < len(reqNames)-1 {
  2114  			comma = ","
  2115  		}
  2116  		fmt.Fprintf(w, "          '%s%s'%s\n", req, requires[req], comma)
  2117  	}
  2118  	fmt.Fprintf(w, "      ],\n")
  2119  
  2120  	fmt.Fprintf(w, "      zip_safe=False)\n")
  2121  	return w.String(), nil
  2122  }
  2123  
  2124  func pep440VersionToSemver(v string) (semver.Version, error) {
  2125  	switch {
  2126  	case pep440AlphaRegex.MatchString(v):
  2127  		parts := pep440AlphaRegex.FindStringSubmatch(v)
  2128  		v = parts[1] + "-alpha." + parts[2]
  2129  	case pep440BetaRegex.MatchString(v):
  2130  		parts := pep440BetaRegex.FindStringSubmatch(v)
  2131  		v = parts[1] + "-beta." + parts[2]
  2132  	case pep440RCRegex.MatchString(v):
  2133  		parts := pep440RCRegex.FindStringSubmatch(v)
  2134  		v = parts[1] + "-rc." + parts[2]
  2135  	case pep440DevRegex.MatchString(v):
  2136  		parts := pep440DevRegex.FindStringSubmatch(v)
  2137  		v = parts[1] + "-dev." + parts[2]
  2138  	}
  2139  
  2140  	return semver.ParseTolerant(v)
  2141  }
  2142  
  2143  // genInitDocstring emits the docstring for the __init__ method of the given resource type.
  2144  //
  2145  // Sphinx (the documentation generator that we use to generate Python docs) does not draw a
  2146  // distinction between documentation comments on the class itself and documentation comments on the
  2147  // __init__ method of a class. The docs repo instructs Sphinx to concatenate the two together, which
  2148  // means that we don't need to emit docstrings on the class at all as long as the __init__ docstring
  2149  // is good enough.
  2150  //
  2151  // The docstring we generate here describes both the class itself and the arguments to the class's
  2152  // constructor. The format of the docstring is in "Sphinx form":
  2153  //  1. Parameters are introduced using the syntax ":param <type> <name>: <comment>". Sphinx parses this and uses it
  2154  //     to populate the list of parameters for this function.
  2155  //  2. The doc string of parameters is expected to be indented to the same indentation as the type of the parameter.
  2156  //     Sphinx will complain and make mistakes if this is not the case.
  2157  //  3. The doc string can't have random newlines in it, or Sphinx will complain.
  2158  //
  2159  // This function does the best it can to navigate these constraints and produce a docstring that
  2160  // Sphinx can make sense of.
  2161  func (mod *modContext) genInitDocstring(w io.Writer, res *schema.Resource, resourceArgsName string, argOverload bool) {
  2162  	// b contains the full text of the docstring, without the leading and trailing triple quotes.
  2163  	b := &bytes.Buffer{}
  2164  
  2165  	// If this resource has documentation, write it at the top of the docstring, otherwise use a generic comment.
  2166  	if res.Comment != "" {
  2167  		fmt.Fprintln(b, codegen.FilterExamples(res.Comment, "python"))
  2168  	} else {
  2169  		fmt.Fprintf(b, "Create a %s resource with the given unique name, props, and options.\n", tokenToName(res.Token))
  2170  	}
  2171  
  2172  	// All resources have a resource_name parameter and opts parameter.
  2173  	fmt.Fprintln(b, ":param str resource_name: The name of the resource.")
  2174  	if argOverload {
  2175  		fmt.Fprintf(b, ":param %s args: The arguments to use to populate this resource's properties.\n",
  2176  			resourceArgsName)
  2177  	}
  2178  	fmt.Fprintln(b, ":param pulumi.ResourceOptions opts: Options for the resource.")
  2179  	if !argOverload {
  2180  		for _, prop := range res.InputProperties {
  2181  			mod.genPropDocstring(b, InitParamName(prop.Name), prop, true /*acceptMapping*/)
  2182  		}
  2183  	}
  2184  
  2185  	// printComment handles the prefix and triple quotes.
  2186  	printComment(w, b.String(), "        ")
  2187  }
  2188  
  2189  func (mod *modContext) genGetDocstring(w io.Writer, res *schema.Resource) {
  2190  	// "buf" contains the full text of the docstring, without the leading and trailing triple quotes.
  2191  	b := &bytes.Buffer{}
  2192  
  2193  	fmt.Fprintf(b, "Get an existing %s resource's state with the given name, id, and optional extra\n"+
  2194  		"properties used to qualify the lookup.\n", tokenToName(res.Token))
  2195  	fmt.Fprintln(b, "")
  2196  
  2197  	fmt.Fprintln(b, ":param str resource_name: The unique name of the resulting resource.")
  2198  	fmt.Fprintln(b, ":param pulumi.Input[str] id: The unique provider ID of the resource to lookup.")
  2199  	fmt.Fprintln(b, ":param pulumi.ResourceOptions opts: Options for the resource.")
  2200  	if res.StateInputs != nil {
  2201  		for _, prop := range res.StateInputs.Properties {
  2202  			mod.genPropDocstring(b, InitParamName(prop.Name), prop, true /*acceptMapping*/)
  2203  		}
  2204  	}
  2205  
  2206  	// printComment handles the prefix and triple quotes.
  2207  	printComment(w, b.String(), "        ")
  2208  }
  2209  
  2210  func (mod *modContext) genTypeDocstring(w io.Writer, comment string, properties []*schema.Property) {
  2211  	// b contains the full text of the docstring, without the leading and trailing triple quotes.
  2212  	b := &bytes.Buffer{}
  2213  
  2214  	// If this type has documentation, write it at the top of the docstring.
  2215  	if comment != "" {
  2216  		fmt.Fprintln(b, comment)
  2217  	}
  2218  
  2219  	for _, prop := range properties {
  2220  		mod.genPropDocstring(b, PyName(prop.Name), prop, false /*acceptMapping*/)
  2221  	}
  2222  
  2223  	// printComment handles the prefix and triple quotes.
  2224  	printComment(w, b.String(), "        ")
  2225  }
  2226  
  2227  func (mod *modContext) genPropDocstring(w io.Writer, name string, prop *schema.Property, acceptMapping bool) {
  2228  	if prop.Comment == "" {
  2229  		return
  2230  	}
  2231  
  2232  	ty := mod.typeString(codegen.RequiredType(prop), true, acceptMapping)
  2233  
  2234  	// If this property has some documentation associated with it, we need to split it so that it is indented
  2235  	// in a way that Sphinx can understand.
  2236  	lines := strings.Split(prop.Comment, "\n")
  2237  	for len(lines) > 0 && lines[len(lines)-1] == "" {
  2238  		lines = lines[:len(lines)-1]
  2239  	}
  2240  	for i, docLine := range lines {
  2241  		// If it's the first line, print the :param header.
  2242  		if i == 0 {
  2243  			fmt.Fprintf(w, ":param %s %s: %s\n", ty, name, docLine)
  2244  		} else {
  2245  			// Otherwise, print out enough padding to align with the first char of the type.
  2246  			fmt.Fprintf(w, "       %s\n", docLine)
  2247  		}
  2248  	}
  2249  }
  2250  
  2251  func (mod *modContext) typeString(t schema.Type, input, acceptMapping bool) string {
  2252  	switch t := t.(type) {
  2253  	case *schema.OptionalType:
  2254  		return fmt.Sprintf("Optional[%s]", mod.typeString(t.ElementType, input, acceptMapping))
  2255  	case *schema.InputType:
  2256  		typ := mod.typeString(codegen.SimplifyInputUnion(t.ElementType), input, acceptMapping)
  2257  		if typ == "Any" {
  2258  			return typ
  2259  		}
  2260  		return fmt.Sprintf("pulumi.Input[%s]", typ)
  2261  	case *schema.EnumType:
  2262  		return mod.tokenToEnum(t.Token)
  2263  	case *schema.ArrayType:
  2264  		return fmt.Sprintf("Sequence[%s]", mod.typeString(t.ElementType, input, acceptMapping))
  2265  	case *schema.MapType:
  2266  		return fmt.Sprintf("Mapping[str, %s]", mod.typeString(t.ElementType, input, acceptMapping))
  2267  	case *schema.ObjectType:
  2268  		typ := mod.objectType(t, input)
  2269  		if !acceptMapping {
  2270  			return typ
  2271  		}
  2272  		return fmt.Sprintf("pulumi.InputType[%s]", typ)
  2273  	case *schema.ResourceType:
  2274  		return fmt.Sprintf("'%s'", mod.resourceType(t))
  2275  	case *schema.TokenType:
  2276  		// Use the underlying type for now.
  2277  		if t.UnderlyingType != nil {
  2278  			return mod.typeString(t.UnderlyingType, input, acceptMapping)
  2279  		}
  2280  		return "Any"
  2281  	case *schema.UnionType:
  2282  		if !input {
  2283  			for _, e := range t.ElementTypes {
  2284  				// If this is an output and a "relaxed" enum, emit the type as the underlying primitive type rather than the union.
  2285  				// Eg. Output[str] rather than Output[Any]
  2286  				if typ, ok := e.(*schema.EnumType); ok {
  2287  					return mod.typeString(typ.ElementType, input, acceptMapping)
  2288  				}
  2289  			}
  2290  			if t.DefaultType != nil {
  2291  				return mod.typeString(t.DefaultType, input, acceptMapping)
  2292  			}
  2293  			return "Any"
  2294  		}
  2295  
  2296  		elementTypeSet := codegen.NewStringSet()
  2297  		elements := make([]string, 0, len(t.ElementTypes))
  2298  		for _, e := range t.ElementTypes {
  2299  			et := mod.typeString(e, input, acceptMapping)
  2300  			if !elementTypeSet.Has(et) {
  2301  				elementTypeSet.Add(et)
  2302  				elements = append(elements, et)
  2303  			}
  2304  		}
  2305  
  2306  		if len(elements) == 1 {
  2307  			return elements[0]
  2308  		}
  2309  		return fmt.Sprintf("Union[%s]", strings.Join(elements, ", "))
  2310  	default:
  2311  		switch t {
  2312  		case schema.BoolType:
  2313  			return "bool"
  2314  		case schema.IntType:
  2315  			return "int"
  2316  		case schema.NumberType:
  2317  			return "float"
  2318  		case schema.StringType:
  2319  			return "str"
  2320  		case schema.ArchiveType:
  2321  			return "pulumi.Archive"
  2322  		case schema.AssetType:
  2323  			return "Union[pulumi.Asset, pulumi.Archive]"
  2324  		case schema.JSONType:
  2325  			fallthrough
  2326  		case schema.AnyType:
  2327  			return "Any"
  2328  		}
  2329  	}
  2330  
  2331  	panic(fmt.Errorf("unexpected type %T", t))
  2332  }
  2333  
  2334  // pyType returns the expected runtime type for the given variable.  Of course, being a dynamic language, this
  2335  // check is not exhaustive, but it should be good enough to catch 80% of the cases early on.
  2336  func (mod *modContext) pyType(typ schema.Type) string {
  2337  	switch typ := typ.(type) {
  2338  	case *schema.OptionalType:
  2339  		return mod.pyType(typ.ElementType)
  2340  	case *schema.EnumType:
  2341  		return mod.pyType(typ.ElementType)
  2342  	case *schema.ArrayType:
  2343  		return "list"
  2344  	case *schema.MapType, *schema.ObjectType, *schema.UnionType:
  2345  		return "dict"
  2346  	case *schema.ResourceType:
  2347  		return mod.resourceType(typ)
  2348  	case *schema.TokenType:
  2349  		if typ.UnderlyingType != nil {
  2350  			return mod.pyType(typ.UnderlyingType)
  2351  		}
  2352  		return "dict"
  2353  	default:
  2354  		switch typ {
  2355  		case schema.BoolType:
  2356  			return "bool"
  2357  		case schema.IntType:
  2358  			return "int"
  2359  		case schema.NumberType:
  2360  			return "float"
  2361  		case schema.StringType:
  2362  			return "str"
  2363  		case schema.ArchiveType:
  2364  			return "pulumi.Archive"
  2365  		case schema.AssetType:
  2366  			return "Union[pulumi.Asset, pulumi.Archive]"
  2367  		default:
  2368  			return "dict"
  2369  		}
  2370  	}
  2371  }
  2372  
  2373  func isStringType(t schema.Type) bool {
  2374  	t = codegen.UnwrapType(t)
  2375  
  2376  	for tt, ok := t.(*schema.TokenType); ok; tt, ok = t.(*schema.TokenType) {
  2377  		t = tt.UnderlyingType
  2378  	}
  2379  
  2380  	return t == schema.StringType
  2381  }
  2382  
  2383  // pyPack returns the suggested package name for the given string.
  2384  func pyPack(s string) string {
  2385  	return "pulumi_" + strings.ReplaceAll(s, "-", "_")
  2386  }
  2387  
  2388  // pyClassName turns a raw name into one that is suitable as a Python class name.
  2389  func pyClassName(name string) string {
  2390  	return EnsureKeywordSafe(name)
  2391  }
  2392  
  2393  // InitParamName returns a PyName-encoded name but also deduplicates the name against built-in parameters of resource __init__.
  2394  func InitParamName(name string) string {
  2395  	result := PyName(name)
  2396  	switch result {
  2397  	case "resource_name", "opts":
  2398  		return result + "_"
  2399  	default:
  2400  		return result
  2401  	}
  2402  }
  2403  
  2404  func (mod *modContext) genObjectType(w io.Writer, obj *schema.ObjectType, input bool) error {
  2405  	name := mod.unqualifiedObjectTypeName(obj, input)
  2406  	resourceOutputType := !input && mod.details(obj).resourceOutputType
  2407  	return mod.genType(w, name, obj.Comment, obj.Properties, input, resourceOutputType)
  2408  }
  2409  
  2410  func (mod *modContext) genType(w io.Writer, name, comment string, properties []*schema.Property, input, resourceOutput bool) error {
  2411  	// Sort required props first.
  2412  	props := make([]*schema.Property, len(properties))
  2413  	copy(props, properties)
  2414  	sort.Slice(props, func(i, j int) bool {
  2415  		pi, pj := props[i], props[j]
  2416  		switch {
  2417  		case pi.IsRequired() != pj.IsRequired():
  2418  			return pi.IsRequired() && !pj.IsRequired()
  2419  		default:
  2420  			return pi.Name < pj.Name
  2421  		}
  2422  	})
  2423  
  2424  	decorator := "@pulumi.output_type"
  2425  	if input {
  2426  		decorator = "@pulumi.input_type"
  2427  	}
  2428  
  2429  	var suffix string
  2430  	if !input {
  2431  		suffix = "(dict)"
  2432  	}
  2433  
  2434  	name = pythonCase(name)
  2435  	fmt.Fprintf(w, "%s\n", decorator)
  2436  	fmt.Fprintf(w, "class %s%s:\n", name, suffix)
  2437  	if !input && comment != "" {
  2438  		printComment(w, comment, "    ")
  2439  	}
  2440  
  2441  	// To help users migrate to using the properly snake_cased property getters, emit warnings when camelCase keys are
  2442  	// accessed. We emit this at the top of the class in case we have a `get` property that will be redefined later.
  2443  	if resourceOutput {
  2444  		var needsCaseWarning bool
  2445  		for _, prop := range props {
  2446  			pname := PyName(prop.Name)
  2447  			if pname != prop.Name {
  2448  				needsCaseWarning = true
  2449  				break
  2450  			}
  2451  		}
  2452  		if needsCaseWarning {
  2453  			fmt.Fprintf(w, "    @staticmethod\n")
  2454  			fmt.Fprintf(w, "    def __key_warning(key: str):\n")
  2455  			fmt.Fprintf(w, "        suggest = None\n")
  2456  			prefix := "if"
  2457  			for _, prop := range props {
  2458  				pname := PyName(prop.Name)
  2459  				if pname == prop.Name {
  2460  					continue
  2461  				}
  2462  				fmt.Fprintf(w, "        %s key == %q:\n", prefix, prop.Name)
  2463  				fmt.Fprintf(w, "            suggest = %q\n", pname)
  2464  				prefix = "elif"
  2465  			}
  2466  			fmt.Fprintf(w, "\n")
  2467  			fmt.Fprintf(w, "        if suggest:\n")
  2468  			fmt.Fprintf(w, "            pulumi.log.warn(f\"Key '{key}' not found in %s. Access the value via the '{suggest}' property getter instead.\")\n", name)
  2469  			fmt.Fprintf(w, "\n")
  2470  			fmt.Fprintf(w, "    def __getitem__(self, key: str) -> Any:\n")
  2471  			fmt.Fprintf(w, "        %s.__key_warning(key)\n", name)
  2472  			fmt.Fprintf(w, "        return super().__getitem__(key)\n")
  2473  			fmt.Fprintf(w, "\n")
  2474  			fmt.Fprintf(w, "    def get(self, key: str, default = None) -> Any:\n")
  2475  			fmt.Fprintf(w, "        %s.__key_warning(key)\n", name)
  2476  			fmt.Fprintf(w, "        return super().get(key, default)\n")
  2477  			fmt.Fprintf(w, "\n")
  2478  		}
  2479  	}
  2480  
  2481  	// Generate an __init__ method.
  2482  	fmt.Fprintf(w, "    def __init__(__self__")
  2483  	// Bare `*` argument to force callers to use named arguments.
  2484  	if len(props) > 0 {
  2485  		fmt.Fprintf(w, ", *")
  2486  	}
  2487  	for _, prop := range props {
  2488  		pname := PyName(prop.Name)
  2489  		ty := mod.typeString(prop.Type, input, false /*acceptMapping*/)
  2490  		var defaultValue string
  2491  		if !prop.IsRequired() {
  2492  			defaultValue = " = None"
  2493  		}
  2494  		fmt.Fprintf(w, ",\n                 %s: %s%s", pname, ty, defaultValue)
  2495  	}
  2496  	fmt.Fprintf(w, "):\n")
  2497  	mod.genTypeDocstring(w, comment, props)
  2498  	if len(props) == 0 {
  2499  		fmt.Fprintf(w, "        pass\n")
  2500  	}
  2501  	for _, prop := range props {
  2502  		pname := PyName(prop.Name)
  2503  		var arg interface{}
  2504  		var err error
  2505  
  2506  		// Fill in computed defaults for arguments.
  2507  		if prop.DefaultValue != nil {
  2508  			dv, err := getDefaultValue(prop.DefaultValue, codegen.UnwrapType(prop.Type))
  2509  			if err != nil {
  2510  				return err
  2511  			}
  2512  			fmt.Fprintf(w, "        if %s is None:\n", pname)
  2513  			fmt.Fprintf(w, "            %s = %s\n", pname, dv)
  2514  		}
  2515  
  2516  		// Check that the property isn't deprecated.
  2517  		if input && prop.DeprecationMessage != "" {
  2518  			escaped := strings.ReplaceAll(prop.DeprecationMessage, `"`, `\"`)
  2519  			fmt.Fprintf(w, "        if %s is not None:\n", pname)
  2520  			fmt.Fprintf(w, "            warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n", escaped)
  2521  			fmt.Fprintf(w, "            pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n", pname, escaped)
  2522  		}
  2523  
  2524  		// And add it to the dictionary.
  2525  		arg = pname
  2526  
  2527  		if prop.ConstValue != nil {
  2528  			arg, err = getConstValue(prop.ConstValue)
  2529  			if err != nil {
  2530  				return err
  2531  			}
  2532  		}
  2533  
  2534  		var indent string
  2535  		if !prop.IsRequired() {
  2536  			fmt.Fprintf(w, "        if %s is not None:\n", pname)
  2537  			indent = "    "
  2538  		}
  2539  
  2540  		fmt.Fprintf(w, "%s        pulumi.set(__self__, \"%s\", %s)\n", indent, pname, arg)
  2541  	}
  2542  	fmt.Fprintf(w, "\n")
  2543  
  2544  	// Generate properties. Input types have getters and setters, output types only have getters.
  2545  	mod.genProperties(w, props, input /*setters*/, "", func(prop *schema.Property) string {
  2546  		return mod.typeString(prop.Type, input, false /*acceptMapping*/)
  2547  	})
  2548  
  2549  	fmt.Fprintf(w, "\n")
  2550  	return nil
  2551  }
  2552  
  2553  func getPrimitiveValue(value interface{}) (string, error) {
  2554  	v := reflect.ValueOf(value)
  2555  	if v.Kind() == reflect.Interface {
  2556  		v = v.Elem()
  2557  	}
  2558  
  2559  	switch v.Kind() {
  2560  	case reflect.Bool:
  2561  		if v.Bool() {
  2562  			return "True", nil
  2563  		}
  2564  		return "False", nil
  2565  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
  2566  		return strconv.FormatInt(v.Int(), 10), nil
  2567  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
  2568  		return strconv.FormatUint(v.Uint(), 10), nil
  2569  	case reflect.Float32, reflect.Float64:
  2570  		return strconv.FormatFloat(v.Float(), 'f', -1, 64), nil
  2571  	case reflect.String:
  2572  		return fmt.Sprintf("'%s'", v.String()), nil
  2573  	default:
  2574  		return "", fmt.Errorf("unsupported default value of type %T", value)
  2575  	}
  2576  }
  2577  
  2578  func getConstValue(cv interface{}) (string, error) {
  2579  	if cv == nil {
  2580  		return "", nil
  2581  	}
  2582  	return getPrimitiveValue(cv)
  2583  }
  2584  
  2585  func getDefaultValue(dv *schema.DefaultValue, t schema.Type) (string, error) {
  2586  	defaultValue := ""
  2587  	if dv.Value != nil {
  2588  		v, err := getPrimitiveValue(dv.Value)
  2589  		if err != nil {
  2590  			return "", err
  2591  		}
  2592  		defaultValue = v
  2593  	}
  2594  
  2595  	if len(dv.Environment) > 0 {
  2596  		envFunc := "_utilities.get_env"
  2597  		switch t {
  2598  		case schema.BoolType:
  2599  			envFunc = "_utilities.get_env_bool"
  2600  		case schema.IntType:
  2601  			envFunc = "_utilities.get_env_int"
  2602  		case schema.NumberType:
  2603  			envFunc = "_utilities.get_env_float"
  2604  		}
  2605  
  2606  		envVars := fmt.Sprintf("'%s'", dv.Environment[0])
  2607  		for _, e := range dv.Environment[1:] {
  2608  			envVars += fmt.Sprintf(", '%s'", e)
  2609  		}
  2610  		if defaultValue == "" {
  2611  			defaultValue = fmt.Sprintf("%s(%s)", envFunc, envVars)
  2612  		} else {
  2613  			defaultValue = fmt.Sprintf("(%s(%s) or %s)", envFunc, envVars, defaultValue)
  2614  		}
  2615  	}
  2616  
  2617  	return defaultValue, nil
  2618  }
  2619  
  2620  func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo, extraFiles map[string][]byte) (map[string]*modContext, error) {
  2621  	// determine whether to use the default Python package name
  2622  	pyPkgName := info.PackageName
  2623  	if pyPkgName == "" {
  2624  		pyPkgName = fmt.Sprintf("pulumi_%s", strings.ReplaceAll(pkg.Name, "-", "_"))
  2625  	}
  2626  
  2627  	// group resources, types, and functions into modules
  2628  	// modules map will contain modContext entries for all modules in current package (pkg)
  2629  	modules := map[string]*modContext{}
  2630  
  2631  	var getMod func(modName string, p *schema.Package) *modContext
  2632  	getMod = func(modName string, p *schema.Package) *modContext {
  2633  		mod, ok := modules[modName]
  2634  		if !ok {
  2635  			mod = &modContext{
  2636  				pkg:                          p,
  2637  				pyPkgName:                    pyPkgName,
  2638  				mod:                          modName,
  2639  				tool:                         tool,
  2640  				modNameOverrides:             info.ModuleNameOverrides,
  2641  				compatibility:                info.Compatibility,
  2642  				liftSingleValueMethodReturns: info.LiftSingleValueMethodReturns,
  2643  			}
  2644  
  2645  			if modName != "" && p == pkg {
  2646  				parentName := path.Dir(modName)
  2647  				if parentName == "." {
  2648  					parentName = ""
  2649  				}
  2650  				parent := getMod(parentName, p)
  2651  				parent.addChild(mod)
  2652  			}
  2653  
  2654  			// Save the module only if it's for the current package.
  2655  			// This way, modules for external packages are not saved.
  2656  			if p == pkg {
  2657  				modules[modName] = mod
  2658  			}
  2659  		}
  2660  		return mod
  2661  	}
  2662  
  2663  	getModFromToken := func(tok string, p *schema.Package) *modContext {
  2664  		modName := tokenToModule(tok, p, info.ModuleNameOverrides)
  2665  		return getMod(modName, p)
  2666  	}
  2667  
  2668  	// Create the config module if necessary.
  2669  	if len(pkg.Config) > 0 &&
  2670  		info.Compatibility != kubernetes20 { // k8s SDK doesn't use config.
  2671  		configMod := getMod("config", pkg)
  2672  		configMod.isConfig = true
  2673  	}
  2674  
  2675  	visitObjectTypes(pkg.Config, func(t schema.Type) {
  2676  		if t, ok := t.(*schema.ObjectType); ok {
  2677  			getModFromToken(t.Token, t.Package).details(t).outputType = true
  2678  		}
  2679  	})
  2680  
  2681  	// Find input and output types referenced by resources.
  2682  	scanResource := func(r *schema.Resource) {
  2683  		mod := getModFromToken(r.Token, pkg)
  2684  		mod.resources = append(mod.resources, r)
  2685  		visitObjectTypes(r.Properties, func(t schema.Type) {
  2686  			switch T := t.(type) {
  2687  			case *schema.ObjectType:
  2688  				getModFromToken(T.Token, T.Package).details(T).outputType = true
  2689  				getModFromToken(T.Token, T.Package).details(T).resourceOutputType = true
  2690  			}
  2691  		})
  2692  		visitObjectTypes(r.InputProperties, func(t schema.Type) {
  2693  			switch T := t.(type) {
  2694  			case *schema.ObjectType:
  2695  				getModFromToken(T.Token, T.Package).details(T).inputType = true
  2696  			}
  2697  		})
  2698  		if r.StateInputs != nil {
  2699  			visitObjectTypes(r.StateInputs.Properties, func(t schema.Type) {
  2700  				switch T := t.(type) {
  2701  				case *schema.ObjectType:
  2702  					getModFromToken(T.Token, T.Package).details(T).inputType = true
  2703  				case *schema.ResourceType:
  2704  					getModFromToken(T.Token, T.Resource.Package)
  2705  				}
  2706  			})
  2707  		}
  2708  	}
  2709  
  2710  	scanResource(pkg.Provider)
  2711  	for _, r := range pkg.Resources {
  2712  		scanResource(r)
  2713  	}
  2714  
  2715  	// Find input and output types referenced by functions.
  2716  	for _, f := range pkg.Functions {
  2717  		mod := getModFromToken(f.Token, f.Package)
  2718  		if !f.IsMethod {
  2719  			mod.functions = append(mod.functions, f)
  2720  		}
  2721  		if f.Inputs != nil {
  2722  			visitObjectTypes(f.Inputs.Properties, func(t schema.Type) {
  2723  				switch T := t.(type) {
  2724  				case *schema.ObjectType:
  2725  					getModFromToken(T.Token, T.Package).details(T).inputType = true
  2726  					getModFromToken(T.Token, T.Package).details(T).plainType = true
  2727  				case *schema.ResourceType:
  2728  					getModFromToken(T.Token, T.Resource.Package)
  2729  				}
  2730  			})
  2731  		}
  2732  		if f.Outputs != nil {
  2733  			visitObjectTypes(f.Outputs.Properties, func(t schema.Type) {
  2734  				switch T := t.(type) {
  2735  				case *schema.ObjectType:
  2736  					getModFromToken(T.Token, T.Package).details(T).outputType = true
  2737  					getModFromToken(T.Token, T.Package).details(T).plainType = true
  2738  				case *schema.ResourceType:
  2739  					getModFromToken(T.Token, T.Resource.Package)
  2740  				}
  2741  			})
  2742  		}
  2743  	}
  2744  
  2745  	// Find nested types.
  2746  	for _, t := range pkg.Types {
  2747  		switch typ := t.(type) {
  2748  		case *schema.ObjectType:
  2749  			mod := getModFromToken(typ.Token, typ.Package)
  2750  			d := mod.details(typ)
  2751  			if d.inputType || d.outputType {
  2752  				mod.types = append(mod.types, typ)
  2753  			}
  2754  		case *schema.EnumType:
  2755  			if !typ.IsOverlay {
  2756  				mod := getModFromToken(typ.Token, pkg)
  2757  				mod.enums = append(mod.enums, typ)
  2758  			}
  2759  		default:
  2760  			continue
  2761  		}
  2762  	}
  2763  
  2764  	// Add python source files to the corresponding modules. Note that we only add the file names; the contents are
  2765  	// still laid out manually in GeneratePackage.
  2766  	for p := range extraFiles {
  2767  		if path.Ext(p) != ".py" {
  2768  			continue
  2769  		}
  2770  
  2771  		modName := path.Dir(p)
  2772  		if modName == "/" || modName == "." {
  2773  			modName = ""
  2774  		}
  2775  		mod := getMod(modName, pkg)
  2776  		mod.extraSourceFiles = append(mod.extraSourceFiles, p)
  2777  	}
  2778  
  2779  	// Setup modLocator so that mod.typeDetails finds the right
  2780  	// modContext for every ObjectType.
  2781  	modLocator := &modLocator{
  2782  		objectTypeMod: func(t *schema.ObjectType) *modContext {
  2783  			if t.Package != pkg {
  2784  				return nil
  2785  			}
  2786  
  2787  			return getModFromToken(t.Token, t.Package)
  2788  		},
  2789  	}
  2790  
  2791  	for _, mod := range modules {
  2792  		mod.modLocator = modLocator
  2793  	}
  2794  
  2795  	return modules, nil
  2796  }
  2797  
  2798  // LanguageResource is derived from the schema and can be used by downstream codegen.
  2799  type LanguageResource struct {
  2800  	*schema.Resource
  2801  
  2802  	Name    string // The resource name (e.g. Deployment)
  2803  	Package string // The package name (e.g. pulumi_kubernetes.apps.v1)
  2804  }
  2805  
  2806  // LanguageResources returns a map of resources that can be used by downstream codegen. The map
  2807  // key is the resource schema token.
  2808  func LanguageResources(tool string, pkg *schema.Package) (map[string]LanguageResource, error) {
  2809  	resources := map[string]LanguageResource{}
  2810  
  2811  	if err := pkg.ImportLanguages(map[string]schema.Language{"python": Importer}); err != nil {
  2812  		return nil, err
  2813  	}
  2814  	info, _ := pkg.Language["python"].(PackageInfo)
  2815  
  2816  	modules, err := generateModuleContextMap(tool, pkg, info, nil)
  2817  	if err != nil {
  2818  		return nil, err
  2819  	}
  2820  
  2821  	for modName, mod := range modules {
  2822  		if modName == "" {
  2823  			continue
  2824  		}
  2825  		for _, r := range mod.resources {
  2826  			if r.IsOverlay {
  2827  				// This resource code is generated by the provider, so no further action is required.
  2828  				continue
  2829  			}
  2830  
  2831  			packagePath := strings.Replace(modName, "/", ".", -1)
  2832  			lr := LanguageResource{
  2833  				Resource: r,
  2834  				Package:  packagePath,
  2835  				Name:     pyClassName(tokenToName(r.Token)),
  2836  			}
  2837  			resources[r.Token] = lr
  2838  		}
  2839  	}
  2840  
  2841  	return resources, nil
  2842  }
  2843  
  2844  func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]byte) (map[string][]byte, error) {
  2845  	// Decode python-specific info
  2846  	if err := pkg.ImportLanguages(map[string]schema.Language{"python": Importer}); err != nil {
  2847  		return nil, err
  2848  	}
  2849  	info, _ := pkg.Language["python"].(PackageInfo)
  2850  
  2851  	modules, err := generateModuleContextMap(tool, pkg, info, extraFiles)
  2852  	if err != nil {
  2853  		return nil, err
  2854  	}
  2855  
  2856  	pkgName := info.PackageName
  2857  	if pkgName == "" {
  2858  		pkgName = pyPack(pkg.Name)
  2859  	}
  2860  
  2861  	files := codegen.Fs{}
  2862  	for p, f := range extraFiles {
  2863  		files.Add(filepath.Join(pkgName, p), f)
  2864  	}
  2865  
  2866  	for _, mod := range modules {
  2867  		if err := mod.gen(files); err != nil {
  2868  			return nil, err
  2869  		}
  2870  	}
  2871  
  2872  	// Generate pulumi-plugin.json
  2873  	plugin, err := genPulumiPluginFile(pkg)
  2874  	if err != nil {
  2875  		return nil, err
  2876  	}
  2877  	files.Add(filepath.Join(pkgName, "pulumi-plugin.json"), plugin)
  2878  
  2879  	// Finally emit the package metadata (setup.py).
  2880  	setup, err := genPackageMetadata(tool, pkg, pkgName, info.Requires, info.PythonRequires)
  2881  	if err != nil {
  2882  		return nil, err
  2883  	}
  2884  	files.Add("setup.py", []byte(setup))
  2885  
  2886  	return files, nil
  2887  }
  2888  
  2889  const utilitiesFile = `
  2890  import importlib.util
  2891  import inspect
  2892  import json
  2893  import os
  2894  import pkg_resources
  2895  import sys
  2896  import typing
  2897  
  2898  import pulumi
  2899  import pulumi.runtime
  2900  
  2901  from semver import VersionInfo as SemverVersion
  2902  from parver import Version as PEP440Version
  2903  
  2904  
  2905  def get_env(*args):
  2906      for v in args:
  2907          value = os.getenv(v)
  2908          if value is not None:
  2909              return value
  2910      return None
  2911  
  2912  
  2913  def get_env_bool(*args):
  2914      str = get_env(*args)
  2915      if str is not None:
  2916          # NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what
  2917          # Terraform uses internally when parsing boolean values.
  2918          if str in ["1", "t", "T", "true", "TRUE", "True"]:
  2919              return True
  2920          if str in ["0", "f", "F", "false", "FALSE", "False"]:
  2921              return False
  2922      return None
  2923  
  2924  
  2925  def get_env_int(*args):
  2926      str = get_env(*args)
  2927      if str is not None:
  2928          try:
  2929              return int(str)
  2930          except:
  2931              return None
  2932      return None
  2933  
  2934  
  2935  def get_env_float(*args):
  2936      str = get_env(*args)
  2937      if str is not None:
  2938          try:
  2939              return float(str)
  2940          except:
  2941              return None
  2942      return None
  2943  
  2944  
  2945  def _get_semver_version():
  2946      # __name__ is set to the fully-qualified name of the current module, In our case, it will be
  2947      # <some module>._utilities. <some module> is the module we want to query the version for.
  2948      root_package, *rest = __name__.split('.')
  2949  
  2950      # pkg_resources uses setuptools to inspect the set of installed packages. We use it here to ask
  2951      # for the currently installed version of the root package (i.e. us) and get its version.
  2952  
  2953      # Unfortunately, PEP440 and semver differ slightly in incompatible ways. The Pulumi engine expects
  2954      # to receive a valid semver string when receiving requests from the language host, so it's our
  2955      # responsibility as the library to convert our own PEP440 version into a valid semver string.
  2956  
  2957      pep440_version_string = pkg_resources.require(root_package)[0].version
  2958      pep440_version = PEP440Version.parse(pep440_version_string)
  2959      (major, minor, patch) = pep440_version.release
  2960      prerelease = None
  2961      if pep440_version.pre_tag == 'a':
  2962          prerelease = f"alpha.{pep440_version.pre}"
  2963      elif pep440_version.pre_tag == 'b':
  2964          prerelease = f"beta.{pep440_version.pre}"
  2965      elif pep440_version.pre_tag == 'rc':
  2966          prerelease = f"rc.{pep440_version.pre}"
  2967      elif pep440_version.dev is not None:
  2968          prerelease = f"dev.{pep440_version.dev}"
  2969  
  2970      # The only significant difference between PEP440 and semver as it pertains to us is that PEP440 has explicit support
  2971      # for dev builds, while semver encodes them as "prerelease" versions. In order to bridge between the two, we convert
  2972      # our dev build version into a prerelease tag. This matches what all of our other packages do when constructing
  2973      # their own semver string.
  2974      return SemverVersion(major=major, minor=minor, patch=patch, prerelease=prerelease)
  2975  
  2976  
  2977  # Determine the version once and cache the value, which measurably improves program performance.
  2978  _version = _get_semver_version()
  2979  _version_str = str(_version)
  2980  
  2981  
  2982  def get_version():
  2983      return _version_str
  2984  
  2985  def get_resource_opts_defaults() -> pulumi.ResourceOptions:
  2986      return pulumi.ResourceOptions(
  2987          version=get_version(),
  2988          plugin_download_url=get_plugin_download_url(),
  2989      )
  2990  
  2991  def get_invoke_opts_defaults() -> pulumi.InvokeOptions:
  2992      return pulumi.InvokeOptions(
  2993          version=get_version(),
  2994          plugin_download_url=get_plugin_download_url(),
  2995      )
  2996  
  2997  def get_resource_args_opts(resource_args_type, resource_options_type, *args, **kwargs):
  2998      """
  2999      Return the resource args and options given the *args and **kwargs of a resource's
  3000      __init__ method.
  3001      """
  3002  
  3003      resource_args, opts = None, None
  3004  
  3005      # If the first item is the resource args type, save it and remove it from the args list.
  3006      if args and isinstance(args[0], resource_args_type):
  3007          resource_args, args = args[0], args[1:]
  3008  
  3009      # Now look at the first item in the args list again.
  3010      # If the first item is the resource options class, save it.
  3011      if args and isinstance(args[0], resource_options_type):
  3012          opts = args[0]
  3013  
  3014      # If resource_args is None, see if "args" is in kwargs, and, if so, if it's typed as the
  3015      # the resource args type.
  3016      if resource_args is None:
  3017          a = kwargs.get("args")
  3018          if isinstance(a, resource_args_type):
  3019              resource_args = a
  3020  
  3021      # If opts is None, look it up in kwargs.
  3022      if opts is None:
  3023          opts = kwargs.get("opts")
  3024  
  3025      return resource_args, opts
  3026  
  3027  
  3028  # Temporary: just use pulumi._utils.lazy_import once everyone upgrades.
  3029  def lazy_import(fullname):
  3030  
  3031      import pulumi._utils as u
  3032      f = getattr(u, 'lazy_import', None)
  3033      if f is None:
  3034          f = _lazy_import_temp
  3035  
  3036      return f(fullname)
  3037  
  3038  
  3039  # Copied from pulumi._utils.lazy_import, see comments there.
  3040  def _lazy_import_temp(fullname):
  3041      m = sys.modules.get(fullname, None)
  3042      if m is not None:
  3043          return m
  3044  
  3045      spec = importlib.util.find_spec(fullname)
  3046  
  3047      m = sys.modules.get(fullname, None)
  3048      if m is not None:
  3049          return m
  3050  
  3051      loader = importlib.util.LazyLoader(spec.loader)
  3052      spec.loader = loader
  3053      module = importlib.util.module_from_spec(spec)
  3054  
  3055      m = sys.modules.get(fullname, None)
  3056      if m is not None:
  3057          return m
  3058  
  3059      sys.modules[fullname] = module
  3060      loader.exec_module(module)
  3061      return module
  3062  
  3063  
  3064  class Package(pulumi.runtime.ResourcePackage):
  3065      def __init__(self, pkg_info):
  3066          super().__init__()
  3067          self.pkg_info = pkg_info
  3068  
  3069      def version(self):
  3070          return _version
  3071  
  3072      def construct_provider(self, name: str, typ: str, urn: str) -> pulumi.ProviderResource:
  3073          if typ != self.pkg_info['token']:
  3074              raise Exception(f"unknown provider type {typ}")
  3075          Provider = getattr(lazy_import(self.pkg_info['fqn']), self.pkg_info['class'])
  3076          return Provider(name, pulumi.ResourceOptions(urn=urn))
  3077  
  3078  
  3079  class Module(pulumi.runtime.ResourceModule):
  3080      def __init__(self, mod_info):
  3081          super().__init__()
  3082          self.mod_info = mod_info
  3083  
  3084      def version(self):
  3085          return _version
  3086  
  3087      def construct(self, name: str, typ: str, urn: str) -> pulumi.Resource:
  3088          class_name = self.mod_info['classes'].get(typ, None)
  3089  
  3090          if class_name is None:
  3091              raise Exception(f"unknown resource type {typ}")
  3092  
  3093          TheClass = getattr(lazy_import(self.mod_info['fqn']), class_name)
  3094          return TheClass(name, pulumi.ResourceOptions(urn=urn))
  3095  
  3096  
  3097  def register(resource_modules, resource_packages):
  3098      resource_modules = json.loads(resource_modules)
  3099      resource_packages = json.loads(resource_packages)
  3100  
  3101      for pkg_info in resource_packages:
  3102          pulumi.runtime.register_resource_package(pkg_info['pkg'], Package(pkg_info))
  3103  
  3104      for mod_info in resource_modules:
  3105          pulumi.runtime.register_resource_module(
  3106              mod_info['pkg'],
  3107              mod_info['mod'],
  3108              Module(mod_info))
  3109  
  3110  
  3111  _F = typing.TypeVar('_F', bound=typing.Callable[..., typing.Any])
  3112  
  3113  
  3114  def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
  3115      """Decorator internally used on {fn}_output lifted function versions
  3116      to implement them automatically from the un-lifted function."""
  3117  
  3118      func_sig = inspect.signature(func)
  3119  
  3120      def lifted_func(*args, opts=None, **kwargs):
  3121          bound_args = func_sig.bind(*args, **kwargs)
  3122          # Convert tuple to list, see pulumi/pulumi#8172
  3123          args_list = list(bound_args.args)
  3124          return pulumi.Output.from_input({
  3125              'args': args_list,
  3126              'kwargs': bound_args.kwargs
  3127          }).apply(lambda resolved_args: func(*resolved_args['args'],
  3128                                              opts=opts,
  3129                                              **resolved_args['kwargs']))
  3130  
  3131      return (lambda _: lifted_func)
  3132  `