2021-08-30 20:52:58 +00:00
// Copyright 2016-2021, Pulumi Corporation.
2020-01-21 22:45:48 +00:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
// goconst linter's warning.
//
2023-01-06 00:07:45 +00:00
//nolint:lll, goconst
2020-01-21 22:45:48 +00:00
package python
import (
"bytes"
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
_ "embed"
2024-07-16 10:55:38 +00:00
"encoding/base64"
2023-03-28 20:42:38 +00:00
"errors"
2020-01-21 22:45:48 +00:00
"fmt"
"io"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
2023-05-02 15:48:45 +00:00
"github.com/BurntSushi/toml"
2020-01-21 22:45:48 +00:00
"github.com/blang/semver"
2021-05-06 18:18:14 +00:00
2021-03-17 13:20:05 +00:00
"github.com/pulumi/pulumi/pkg/v3/codegen"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
2021-09-08 05:23:30 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
2021-03-17 13:20:05 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
2023-06-28 16:02:04 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
2021-09-08 05:23:30 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
2021-03-17 13:20:05 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
2020-01-21 22:45:48 +00:00
)
2024-06-18 15:24:42 +00:00
const (
InputTypesSettingClasses = "classes"
InputTypesSettingClassesAndDicts = "classes-and-dicts"
)
func typedDictEnabled ( setting string ) bool {
2024-07-23 11:26:54 +00:00
return setting != InputTypesSettingClasses
2024-06-18 15:24:42 +00:00
}
2020-08-19 08:16:47 +00:00
type typeDetails struct {
2021-04-19 23:40:39 +00:00
outputType bool
inputType bool
resourceOutputType bool
plainType bool
2020-08-19 08:16:47 +00:00
}
2021-03-01 17:54:25 +00:00
type imports codegen . StringSet
2020-08-19 08:16:47 +00:00
2023-03-28 20:19:46 +00:00
// defaultMinPythonVersion is what we use as the minimum version field in generated
2024-02-04 19:07:53 +00:00
// package metadata if the schema does not provide a value. This version corresponds
2023-03-28 20:19:46 +00:00
// to the minimum supported version as listed in the reference documentation:
2024-02-04 19:07:53 +00:00
// https://www.pulumi.com/docs/languages-sdks/python/
const defaultMinPythonVersion = ">=3.8"
2023-03-28 20:19:46 +00:00
2021-02-12 23:54:19 +00:00
func ( imports imports ) addType ( mod * modContext , t * schema . ObjectType , input bool ) {
imports . addTypeIf ( mod , t , input , nil /*predicate*/ )
2020-08-19 08:16:47 +00:00
}
2021-02-12 23:54:19 +00:00
func ( imports imports ) addTypeIf ( mod * modContext , t * schema . ObjectType , input bool , predicate func ( imp string ) bool ) {
if imp := mod . importObjectType ( t , input ) ; imp != "" && ( predicate == nil || predicate ( imp ) ) {
2021-03-01 17:54:25 +00:00
codegen . StringSet ( imports ) . Add ( imp )
2020-09-23 19:39:25 +00:00
}
}
2022-05-12 08:59:56 +00:00
func ( imports imports ) addEnum ( mod * modContext , enum * schema . EnumType ) {
if imp := mod . importEnumType ( enum ) ; imp != "" {
2021-03-01 17:54:25 +00:00
codegen . StringSet ( imports ) . Add ( imp )
2020-11-25 05:43:32 +00:00
}
}
2021-02-12 23:54:19 +00:00
func ( imports imports ) addResource ( mod * modContext , r * schema . ResourceType ) {
if imp := mod . importResourceType ( r ) ; imp != "" {
2021-03-01 17:54:25 +00:00
codegen . StringSet ( imports ) . Add ( imp )
2020-08-19 08:16:47 +00:00
}
}
func ( imports imports ) strings ( ) [ ] string {
2023-06-28 16:02:04 +00:00
result := slice . Prealloc [ string ] ( len ( imports ) )
2020-08-19 08:16:47 +00:00
for imp := range imports {
result = append ( result , imp )
}
sort . Strings ( result )
return result
}
2020-01-21 22:45:48 +00:00
func title ( s string ) string {
if s == "" {
return ""
}
runes := [ ] rune ( s )
return string ( append ( [ ] rune { unicode . ToUpper ( runes [ 0 ] ) } , runes [ 1 : ] ... ) )
}
2022-10-05 19:22:06 +00:00
type modLocator struct {
2024-06-18 15:24:42 +00:00
// Returns defining module for a given ObjectType. Returns nil
2022-10-05 19:22:06 +00:00
// for types that are not being generated in the current
2024-06-18 15:24:42 +00:00
// GeneratePackage call.
2022-10-05 19:22:06 +00:00
objectTypeMod func ( * schema . ObjectType ) * modContext
}
2020-01-21 22:45:48 +00:00
type modContext struct {
2022-12-07 12:03:41 +00:00
pkg schema . PackageReference
2022-10-05 19:22:06 +00:00
modLocator * modLocator
2021-07-27 15:37:49 +00:00
mod string
pyPkgName string
types [ ] * schema . ObjectType
enums [ ] * schema . EnumType
resources [ ] * schema . Resource
functions [ ] * schema . Function
typeDetails map [ * schema . ObjectType ] * typeDetails
children [ ] * modContext
parent * modContext
tool string
extraSourceFiles [ ] string
isConfig bool
2020-06-09 21:33:02 +00:00
// Name overrides set in PackageInfo
modNameOverrides map [ string ] string // Optional overrides for Pulumi module names
2020-06-10 20:55:10 +00:00
compatibility string // Toggle compatibility mode for a specified target.
2021-10-01 18:33:02 +00:00
// Determine whether to lift single-value method return values
liftSingleValueMethodReturns bool
2024-06-18 15:24:42 +00:00
// Controls what types are used for inputs, see PackageInfo.InputTypes.
inputTypes string
2020-01-21 22:45:48 +00:00
}
2021-04-29 20:08:22 +00:00
func ( mod * modContext ) isTopLevel ( ) bool {
return mod . parent == nil
}
func ( mod * modContext ) walkSelfWithDescendants ( ) [ ] * modContext {
var found [ ] * modContext
found = append ( found , mod )
for _ , childMod := range mod . children {
found = append ( found , childMod . walkSelfWithDescendants ( ) ... )
}
return found
}
func ( mod * modContext ) addChild ( child * modContext ) {
mod . children = append ( mod . children , child )
child . parent = mod
}
2020-08-19 08:16:47 +00:00
func ( mod * modContext ) details ( t * schema . ObjectType ) * typeDetails {
2022-10-05 19:22:06 +00:00
m := mod
if mod . modLocator != nil {
if actualMod := mod . modLocator . objectTypeMod ( t ) ; actualMod != nil {
m = actualMod
}
}
details , ok := m . typeDetails [ t ]
2020-08-19 08:16:47 +00:00
if ! ok {
details = & typeDetails { }
2022-10-05 19:22:06 +00:00
if m . typeDetails == nil {
m . typeDetails = map [ * schema . ObjectType ] * typeDetails { }
2020-08-19 08:16:47 +00:00
}
2022-10-05 19:22:06 +00:00
m . typeDetails [ t ] = details
2020-08-19 08:16:47 +00:00
}
return details
}
2022-12-07 12:03:41 +00:00
func ( mod * modContext ) modNameAndName ( pkg schema . PackageReference , t schema . Type , input bool ) ( modName string , name string ) {
2021-02-12 23:54:19 +00:00
var info PackageInfo
2022-12-07 12:03:41 +00:00
p , err := pkg . Definition ( )
2023-02-21 23:27:34 +00:00
contract . AssertNoErrorf ( err , "error loading definition for package %q" , pkg . Name ( ) )
contract . AssertNoErrorf ( p . ImportLanguages ( map [ string ] schema . Language { "python" : Importer } ) ,
"error importing python language plugin for package %q" , pkg . Name ( ) )
2022-12-07 12:03:41 +00:00
if v , ok := p . Language [ "python" ] . ( PackageInfo ) ; ok {
2021-02-12 23:54:19 +00:00
info = v
}
2021-04-19 23:40:39 +00:00
var token string
switch t := t . ( type ) {
case * schema . EnumType :
token , name = t . Token , tokenToName ( t . Token )
case * schema . ObjectType :
namingCtx := & modContext {
pkg : pkg ,
modNameOverrides : info . ModuleNameOverrides ,
compatibility : info . Compatibility ,
}
2021-06-24 16:17:55 +00:00
token , name = t . Token , namingCtx . unqualifiedObjectTypeName ( t , input )
2021-04-19 23:40:39 +00:00
case * schema . ResourceType :
token , name = t . Token , tokenToName ( t . Token )
}
modName = tokenToModule ( token , pkg , info . ModuleNameOverrides )
2021-02-12 23:54:19 +00:00
if modName != "" {
modName = strings . ReplaceAll ( modName , "/" , "." ) + "."
}
return
}
2021-06-24 16:17:55 +00:00
func ( mod * modContext ) unqualifiedObjectTypeName ( t * schema . ObjectType , input bool ) string {
2021-04-16 02:03:28 +00:00
name := tokenToName ( t . Token )
2021-02-12 23:54:19 +00:00
2021-04-16 18:30:25 +00:00
if mod . compatibility != tfbridge20 && mod . compatibility != kubernetes20 {
2021-06-24 16:17:55 +00:00
if t . IsInputShape ( ) {
2021-04-16 02:03:28 +00:00
return name + "Args"
}
return name
2021-02-12 23:54:19 +00:00
}
2020-08-19 08:16:47 +00:00
switch {
case input :
2021-04-16 02:03:28 +00:00
return name + "Args"
2021-04-19 23:40:39 +00:00
case mod . details ( t ) . plainType :
2021-04-16 02:03:28 +00:00
return name + "Result"
}
return name
}
2024-06-18 15:24:42 +00:00
func ( mod * modContext ) objectType ( t * schema . ObjectType , input bool , forDict bool ) string {
2021-04-16 02:03:28 +00:00
var prefix string
if ! input {
prefix = "outputs."
2020-08-19 08:16:47 +00:00
}
2021-02-12 23:54:19 +00:00
// If it's an external type, reference it via fully qualified name.
2022-12-07 12:03:41 +00:00
if ! codegen . PkgEquals ( t . PackageReference , mod . pkg ) {
modName , name := mod . modNameAndName ( t . PackageReference , t , input )
2024-07-23 11:26:54 +00:00
if forDict {
pkg , err := t . PackageReference . Definition ( )
contract . AssertNoErrorf ( err , "error loading definition for package %q" , t . PackageReference . Name ( ) )
info , ok := pkg . Language [ "python" ] . ( PackageInfo )
// TODO[https://github.com/pulumi/pulumi/issues/16702]
// We don't yet assume that external packages support TypedDicts by default.
// Remove empty string check to enable TypedDicts for external packages by default.
typedDicts := ok && typedDictEnabled ( info . InputTypes ) && info . InputTypes != ""
if typedDicts {
name = name + "Dict"
}
}
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
return fmt . Sprintf ( "'%s.%s%s%s'" , pyPack ( t . PackageReference . Name ( ) ) , modName , prefix , name )
2021-02-12 23:54:19 +00:00
}
2021-06-24 16:17:55 +00:00
modName , name := mod . tokenToModule ( t . Token ) , mod . unqualifiedObjectTypeName ( t , input )
2024-06-18 15:24:42 +00:00
if forDict {
name = name + "Dict"
}
2020-08-19 08:16:47 +00:00
if modName == "" && modName != mod . mod {
rootModName := "_root_outputs."
if input {
rootModName = "_root_inputs."
}
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
return fmt . Sprintf ( "'%s%s'" , rootModName , name )
2020-08-19 08:16:47 +00:00
}
if modName == mod . mod {
modName = ""
}
if modName != "" {
modName = "_" + strings . ReplaceAll ( modName , "/" , "." ) + "."
}
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
return fmt . Sprintf ( "'%s%s%s'" , modName , prefix , name )
2020-08-19 08:16:47 +00:00
}
2023-07-25 18:57:07 +00:00
func ( mod * modContext ) enumType ( enum * schema . EnumType ) string {
tok := enum . Token
pkgName , modName , name := enum . PackageReference . Name ( ) , mod . tokenToModule ( tok ) , tokenToName ( tok )
2020-11-25 05:43:32 +00:00
2023-07-25 18:57:07 +00:00
if pkgName == mod . pkg . Name ( ) && modName == "" && mod . mod != "" {
2020-11-25 05:43:32 +00:00
return fmt . Sprintf ( "'_root_enums.%s'" , name )
}
2023-07-25 18:57:07 +00:00
parts := [ ] string { }
// Add package name if needed.
if pkgName != mod . pkg . Name ( ) {
// Foreign reference. Add package import alias.
parts = append ( parts , pyPack ( pkgName ) )
2020-11-25 05:43:32 +00:00
}
2023-07-25 18:57:07 +00:00
// Add module name if needed.
2020-11-25 05:43:32 +00:00
if modName != "" {
2023-07-25 18:57:07 +00:00
if pkgName == mod . pkg . Name ( ) && modName == mod . mod {
// Enum is in the same module, don't add the module name.
} else {
// Add the module name because it's referencing a different module.
parts = append ( parts , strings . ReplaceAll ( modName , "/" , "." ) )
}
2020-11-25 05:43:32 +00:00
}
2023-07-25 18:57:07 +00:00
parts = append ( parts , name )
2020-11-25 05:43:32 +00:00
2023-07-25 18:57:07 +00:00
return fmt . Sprintf ( "'%s'" , strings . Join ( parts , "." ) )
2020-11-25 05:43:32 +00:00
}
2021-02-12 23:54:19 +00:00
func ( mod * modContext ) resourceType ( r * schema . ResourceType ) string {
2022-12-07 12:03:41 +00:00
if r . Resource == nil || codegen . PkgEquals ( r . Resource . PackageReference , mod . pkg ) {
2021-02-12 23:54:19 +00:00
return mod . tokenToResource ( r . Token )
}
// Is it a provider resource?
if strings . HasPrefix ( r . Token , "pulumi:providers:" ) {
pkgName := strings . TrimPrefix ( r . Token , "pulumi:providers:" )
return fmt . Sprintf ( "pulumi_%s.Provider" , pkgName )
}
2022-12-07 12:03:41 +00:00
pkg := r . Resource . PackageReference
2021-06-24 16:17:55 +00:00
modName , name := mod . modNameAndName ( pkg , r , false )
2022-12-07 12:03:41 +00:00
return fmt . Sprintf ( "%s.%s%s" , pyPack ( pkg . Name ( ) ) , modName , name )
2021-02-12 23:54:19 +00:00
}
2020-09-23 19:39:25 +00:00
func ( mod * modContext ) tokenToResource ( tok string ) string {
// token := pkg : module : member
// module := path/to/module
components := strings . Split ( tok , ":" )
contract . Assertf ( len ( components ) == 3 , "malformed token %v" , tok )
2020-11-23 20:28:00 +00:00
// Is it a provider resource?
if components [ 0 ] == "pulumi" && components [ 1 ] == "providers" {
return fmt . Sprintf ( "pulumi_%s.Provider" , components [ 2 ] )
}
2020-11-25 05:43:32 +00:00
modName , name := mod . tokenToModule ( tok ) , tokenToName ( tok )
2020-09-23 19:39:25 +00:00
if modName == mod . mod {
modName = ""
}
if modName != "" {
modName = "_" + strings . ReplaceAll ( modName , "/" , "." ) + "."
}
return fmt . Sprintf ( "%s%s" , modName , name )
}
2020-01-21 22:45:48 +00:00
func tokenToName ( tok string ) string {
2020-11-25 05:43:32 +00:00
// token := pkg : module : member
// module := path/to/module
2020-01-21 22:45:48 +00:00
components := strings . Split ( tok , ":" )
contract . Assertf ( len ( components ) == 3 , "malformed token %v" , tok )
2020-11-25 05:43:32 +00:00
2020-01-21 22:45:48 +00:00
return title ( components [ 2 ] )
}
2022-12-07 12:03:41 +00:00
func tokenToModule ( tok string , pkg schema . PackageReference , moduleNameOverrides map [ string ] string ) string {
2021-06-22 22:43:21 +00:00
// See if there's a manually-overridden module name.
2022-12-07 12:03:41 +00:00
if pkg == nil {
// If pkg is nil, we use the default `TokenToModule` scheme.
pkg = ( & schema . Package { } ) . Reference ( )
}
2020-08-19 08:16:47 +00:00
canonicalModName := pkg . TokenToModule ( tok )
if override , ok := moduleNameOverrides [ canonicalModName ] ; ok {
2021-06-22 22:43:21 +00:00
return override
}
// A module can include fileparts, which we want to preserve.
var modName string
for i , part := range strings . Split ( strings . ToLower ( canonicalModName ) , "/" ) {
if i > 0 {
modName += "/"
}
modName += PyName ( part )
2020-08-19 08:16:47 +00:00
}
return modName
}
func ( mod * modContext ) tokenToModule ( tok string ) string {
return tokenToModule ( tok , mod . pkg , mod . modNameOverrides )
}
2020-01-21 22:45:48 +00:00
func printComment ( w io . Writer , comment string , indent string ) {
lines := strings . Split ( comment , "\n" )
for len ( lines ) > 0 && lines [ len ( lines ) - 1 ] == "" {
lines = lines [ : len ( lines ) - 1 ]
}
if len ( lines ) == 0 {
return
}
2021-04-13 22:10:23 +00:00
// Known special characters that need escaping.
2022-07-07 07:21:30 +00:00
replacer := strings . NewReplacer ( ` \ ` , ` \\ ` , ` """ ` , ` \"\"\" ` )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , "%s\"\"\"\n" , indent )
for _ , l := range lines {
if l == "" {
fmt . Fprintf ( w , "\n" )
} else {
2020-06-30 09:19:45 +00:00
escaped := replacer . Replace ( l )
2020-05-05 19:39:35 +00:00
fmt . Fprintf ( w , "%s%s\n" , indent , escaped )
2020-01-21 22:45:48 +00:00
}
}
fmt . Fprintf ( w , "%s\"\"\"\n" , indent )
}
2021-08-30 20:52:58 +00:00
func genStandardHeader ( w io . Writer , tool string ) {
2020-01-21 22:45:48 +00:00
// Set the encoding to UTF-8, in case the comments contain non-ASCII characters.
fmt . Fprintf ( w , "# coding=utf-8\n" )
// Emit a standard warning header ("do not edit", etc).
2021-08-30 20:52:58 +00:00
fmt . Fprintf ( w , "# *** WARNING: this file was generated by %v. ***\n" , tool )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , "# *** Do not edit by hand unless you're certain you know what you are doing! ***\n\n" )
2021-08-30 20:52:58 +00:00
}
2022-12-09 13:36:44 +00:00
func typingImports ( ) [ ] string {
return [ ] string {
"Any" ,
"Mapping" ,
"Optional" ,
"Sequence" ,
"Union" ,
"overload" ,
}
}
func ( mod * modContext ) generateCommonImports ( w io . Writer , imports imports , typingImports [ ] string ) {
rel , err := filepath . Rel ( mod . mod , "" )
2023-02-21 23:27:34 +00:00
contract . AssertNoErrorf ( err , "could not turn %q into a relative path" , mod . mod )
2022-12-09 13:36:44 +00:00
relRoot := path . Dir ( rel )
relImport := relPathToRelImport ( relRoot )
fmt . Fprintf ( w , "import copy\n" )
fmt . Fprintf ( w , "import warnings\n" )
2024-06-18 15:24:42 +00:00
if typedDictEnabled ( mod . inputTypes ) {
fmt . Fprintf ( w , "import sys\n" )
}
2022-12-09 13:36:44 +00:00
fmt . Fprintf ( w , "import pulumi\n" )
fmt . Fprintf ( w , "import pulumi.runtime\n" )
fmt . Fprintf ( w , "from typing import %s\n" , strings . Join ( typingImports , ", " ) )
2024-06-18 15:24:42 +00:00
if typedDictEnabled ( mod . inputTypes ) {
fmt . Fprintf ( w , "if sys.version_info >= (3, 11):\n" )
fmt . Fprintf ( w , " from typing import NotRequired, TypedDict, TypeAlias\n" )
fmt . Fprintf ( w , "else:\n" )
fmt . Fprintf ( w , " from typing_extensions import NotRequired, TypedDict, TypeAlias\n" )
}
2022-12-09 13:36:44 +00:00
fmt . Fprintf ( w , "from %s import _utilities\n" , relImport )
for _ , imp := range imports . strings ( ) {
fmt . Fprintf ( w , "%s\n" , imp )
}
fmt . Fprintf ( w , "\n" )
}
2021-08-30 20:52:58 +00:00
func ( mod * modContext ) genHeader ( w io . Writer , needsSDK bool , imports imports ) {
genStandardHeader ( w , mod . tool )
2020-01-21 22:45:48 +00:00
// If needed, emit the standard Pulumi SDK import statement.
if needsSDK {
2022-12-09 13:36:44 +00:00
typings := typingImports ( )
mod . generateCommonImports ( w , imports , typings )
2020-01-21 22:45:48 +00:00
}
}
2022-12-09 13:36:44 +00:00
func ( mod * modContext ) genFunctionHeader ( w io . Writer , function * schema . Function , imports imports ) {
genStandardHeader ( w , mod . tool )
typings := typingImports ( )
if function . Outputs == nil || len ( function . Outputs . Properties ) == 0 {
typings = append ( typings , "Awaitable" )
}
mod . generateCommonImports ( w , imports , typings )
}
2020-06-11 16:10:53 +00:00
func relPathToRelImport ( relPath string ) string {
// Convert relative path to relative import e.g. "../.." -> "..."
// https://realpython.com/absolute-vs-relative-python-imports/#relative-imports
relImport := "."
2020-06-11 22:31:39 +00:00
if relPath == "." {
return relImport
}
2020-06-11 16:10:53 +00:00
for _ , component := range strings . Split ( relPath , "/" ) {
if component == ".." {
relImport += "."
} else {
relImport += component
}
}
return relImport
}
2022-12-07 12:03:41 +00:00
func ( mod * modContext ) genUtilitiesFile ( ) ( [ ] byte , error ) {
2021-08-30 20:52:58 +00:00
buffer := & bytes . Buffer { }
2022-01-11 16:26:48 +00:00
genStandardHeader ( buffer , mod . tool )
2023-01-13 18:56:53 +00:00
fmt . Fprint ( buffer , utilitiesFile )
2022-06-15 07:28:41 +00:00
optionalURL := "None"
2022-12-07 12:03:41 +00:00
pkg , err := mod . pkg . Definition ( )
if err != nil {
return nil , err
}
if url := pkg . PluginDownloadURL ; url != "" {
2022-06-15 07:28:41 +00:00
optionalURL = fmt . Sprintf ( "%q" , url )
2022-01-11 16:26:48 +00:00
}
2022-12-07 12:03:41 +00:00
_ , err = fmt . Fprintf ( buffer , `
2022-06-14 23:52:24 +00:00
def get_plugin_download_url ( ) :
return % s
2022-06-15 07:28:41 +00:00
` , optionalURL )
2024-06-29 10:07:14 +00:00
if err != nil {
return nil , err
}
// If a new style support pack library is being generated then write the _actual_ version to the utilities file
// rather than relying on re-parsing the pypi version from setup.py.
if pkg . SupportPack {
if pkg . Version == nil {
return nil , errors . New ( "package version is required" )
}
_ , err = fmt . Fprintf ( buffer , `
def get_version ( ) :
return % q
` , pkg . Version . String ( ) )
} else {
_ , err = fmt . Fprintf ( buffer , `
def get_version ( ) :
return _version_str
` )
}
2024-07-16 10:55:38 +00:00
if err != nil {
return nil , err
}
if pkg . Parameterization != nil {
// If a parameterized package is being generated then we _need_ to use package references
param := base64 . StdEncoding . EncodeToString ( pkg . Parameterization . Parameter )
_ , err = fmt . Fprintf ( buffer , `
_package_lock = asyncio . Lock ( )
_package_ref = ...
async def get_package ( ) :
global _package_ref
# TODO : This should check a feature flag for if RegisterPackage is supported .
if _package_ref is ... :
async with _package_lock :
if _package_ref is ... :
monitor = pulumi . runtime . settings . get_monitor ( )
parameterization = resource_pb2 . Parameterization (
name = % q ,
version = get_version ( ) ,
value = base64 . b64decode ( % q ) ,
)
registerPackageResponse = monitor . RegisterPackage (
resource_pb2 . RegisterPackageRequest (
name = % q ,
version = % q ,
download_url = get_plugin_download_url ( ) ,
parameterization = parameterization ,
) )
_package_ref = registerPackageResponse . ref
# TODO : This check is only needed for paramaterised providers , normal providers can return None for get_package when we start
# using package with them .
if _package_ref is None :
raise Exception ( "The Pulumi CLI does not support package references. Please update the Pulumi CLI." )
return _package_ref
` ,
pkg . Name , param , pkg . Parameterization . BaseProvider . Name , pkg . Parameterization . BaseProvider . Version )
if err != nil {
return nil , err
}
}
2024-06-29 10:07:14 +00:00
2024-07-16 10:55:38 +00:00
return buffer . Bytes ( ) , nil
2021-08-30 20:52:58 +00:00
}
2022-11-09 19:49:59 +00:00
func ( mod * modContext ) gen ( fs codegen . Fs ) error {
2021-05-06 18:18:14 +00:00
dir := path . Join ( mod . pyPkgName , mod . mod )
2020-01-21 22:45:48 +00:00
var exports [ ] string
for p := range fs {
d := path . Dir ( p )
if d == "." {
d = ""
}
if d == dir {
2020-03-09 16:55:26 +00:00
exports = append ( exports , strings . TrimSuffix ( path . Base ( p ) , ".py" ) )
2020-01-21 22:45:48 +00:00
}
}
addFile := func ( name , contents string ) {
p := path . Join ( dir , name )
2021-07-14 21:26:50 +00:00
if ! strings . HasSuffix ( name , ".pyi" ) {
exports = append ( exports , name [ : len ( name ) - len ( ".py" ) ] )
}
2022-11-09 19:49:59 +00:00
fs . Add ( p , [ ] byte ( contents ) )
2020-01-21 22:45:48 +00:00
}
2020-06-08 20:07:56 +00:00
// Utilities, config, readme
2020-01-21 22:45:48 +00:00
switch mod . mod {
case "" :
2022-12-07 12:03:41 +00:00
utils , err := mod . genUtilitiesFile ( )
if err != nil {
return err
}
fs . Add ( filepath . Join ( dir , "_utilities.py" ) , utils )
2022-11-09 19:49:59 +00:00
fs . Add ( filepath . Join ( dir , "py.typed" ) , [ ] byte { } )
2020-01-21 22:45:48 +00:00
2020-06-08 20:07:56 +00:00
// Ensure that the top-level (provider) module directory contains a README.md file.
2021-10-04 21:26:49 +00:00
2022-12-07 12:03:41 +00:00
pkg , err := mod . pkg . Definition ( )
if err != nil {
return err
}
2021-10-04 21:26:49 +00:00
var readme string
2022-12-07 12:03:41 +00:00
if pythonInfo , ok := pkg . Language [ "python" ] ; ok {
2021-10-04 21:26:49 +00:00
if typedInfo , ok := pythonInfo . ( PackageInfo ) ; ok {
readme = typedInfo . Readme
}
}
2020-06-08 20:07:56 +00:00
if readme == "" {
2022-12-07 12:03:41 +00:00
readme = mod . pkg . Description ( )
2020-06-08 20:07:56 +00:00
if readme != "" && readme [ len ( readme ) - 1 ] != '\n' {
readme += "\n"
}
2022-12-07 12:03:41 +00:00
if pkg . Attribution != "" {
2020-06-08 20:07:56 +00:00
if len ( readme ) != 0 {
readme += "\n"
}
2022-12-07 12:03:41 +00:00
readme += pkg . Attribution
2020-06-08 20:07:56 +00:00
}
if readme != "" && readme [ len ( readme ) - 1 ] != '\n' {
readme += "\n"
}
}
2022-11-09 19:49:59 +00:00
fs . Add ( filepath . Join ( dir , "README.md" ) , [ ] byte ( readme ) )
2020-06-08 20:07:56 +00:00
2020-01-21 22:45:48 +00:00
case "config" :
2022-12-07 12:03:41 +00:00
config , err := mod . pkg . Config ( )
if err != nil {
return err
}
if len ( config ) > 0 {
vars , err := mod . genConfig ( config )
2020-01-21 22:45:48 +00:00
if err != nil {
return err
}
addFile ( "vars.py" , vars )
2022-12-07 12:03:41 +00:00
typeStubs , err := mod . genConfigStubs ( config )
2021-07-14 21:26:50 +00:00
if err != nil {
return err
}
addFile ( "__init__.pyi" , typeStubs )
2020-01-21 22:45:48 +00:00
}
}
// Resources
for _ , r := range mod . resources {
2021-11-12 00:00:03 +00:00
if r . IsOverlay {
// This resource code is generated by the provider, so no further action is required.
continue
}
2020-01-21 22:45:48 +00:00
res , err := mod . genResource ( r )
if err != nil {
return err
}
name := PyName ( tokenToName ( r . Token ) )
2020-06-23 20:02:22 +00:00
if mod . compatibility == kubernetes20 {
// To maintain backward compatibility for kubernetes, the file names
// need to be CamelCase instead of the standard snake_case.
name = tokenToName ( r . Token )
}
2020-01-21 22:45:48 +00:00
if r . IsProvider {
2020-02-11 22:34:22 +00:00
name = "provider"
2020-01-21 22:45:48 +00:00
}
addFile ( name + ".py" , res )
}
// Functions
for _ , f := range mod . functions {
2021-11-12 00:00:03 +00:00
if f . IsOverlay {
// This function code is generated by the provider, so no further action is required.
continue
}
2023-01-11 22:17:14 +00:00
if f . MultiArgumentInputs {
return fmt . Errorf ( "python SDK-gen does not implement MultiArgumentInputs for function '%s'" ,
f . Token )
}
2020-01-21 22:45:48 +00:00
fun , err := mod . genFunction ( f )
if err != nil {
return err
}
addFile ( PyName ( tokenToName ( f . Token ) ) + ".py" , fun )
}
2020-08-19 08:16:47 +00:00
// Nested types
if len ( mod . types ) > 0 {
if err := mod . genTypes ( dir , fs ) ; err != nil {
return err
}
}
2020-11-25 05:43:32 +00:00
// Enums
if len ( mod . enums ) > 0 {
buffer := & bytes . Buffer { }
if err := mod . genEnums ( buffer , mod . enums ) ; err != nil {
return err
}
addFile ( "_enums.py" , buffer . String ( ) )
}
2020-01-21 22:45:48 +00:00
// Index
2020-08-19 08:16:47 +00:00
if ! mod . isEmpty ( ) {
2022-11-09 19:49:59 +00:00
fs . Add ( path . Join ( dir , "__init__.py" ) , [ ] byte ( mod . genInit ( exports ) ) )
2020-08-19 08:16:47 +00:00
}
2020-01-21 22:45:48 +00:00
return nil
}
2020-08-19 08:16:47 +00:00
func ( mod * modContext ) hasTypes ( input bool ) bool {
2021-11-12 00:00:03 +00:00
if allTypesAreOverlays ( mod . types ) {
return false
}
2020-08-19 08:16:47 +00:00
for _ , t := range mod . types {
if input && mod . details ( t ) . inputType {
return true
}
if ! input && mod . details ( t ) . outputType {
return true
}
}
return false
}
func ( mod * modContext ) isEmpty ( ) bool {
2020-08-20 18:07:09 +00:00
if len ( mod . extraSourceFiles ) > 0 || len ( mod . functions ) > 0 || len ( mod . resources ) > 0 || len ( mod . types ) > 0 ||
2024-05-20 14:16:49 +00:00
len ( mod . enums ) > 0 || mod . isConfig {
2020-08-19 08:16:47 +00:00
return false
}
for _ , child := range mod . children {
if ! child . isEmpty ( ) {
return false
}
}
return true
}
2020-06-24 18:41:03 +00:00
func ( mod * modContext ) submodulesExist ( ) bool {
2022-07-29 15:07:27 +00:00
for _ , submod := range mod . children {
if ! submod . isEmpty ( ) {
return true
}
}
return false
2020-06-24 18:41:03 +00:00
}
2021-04-29 20:08:22 +00:00
func ( mod * modContext ) unqualifiedImportName ( ) string {
name := mod . mod
// Extract version suffix from child modules. Nested versions will have their own __init__.py file.
// Example: apps/v1beta1 -> v1beta1
2021-06-24 21:01:49 +00:00
parts := strings . Split ( name , "/" )
if len ( parts ) > 1 {
name = parts [ len ( parts ) - 1 ]
2021-04-29 20:08:22 +00:00
}
return PyName ( name )
}
func ( mod * modContext ) fullyQualifiedImportName ( ) string {
name := mod . unqualifiedImportName ( )
if mod . parent == nil && name == "" {
2021-05-06 18:18:14 +00:00
return mod . pyPkgName
2021-04-29 20:08:22 +00:00
}
if mod . parent == nil {
2022-12-07 12:03:41 +00:00
return fmt . Sprintf ( "%s.%s" , pyPack ( mod . pkg . Name ( ) ) , name )
2021-04-29 20:08:22 +00:00
}
return fmt . Sprintf ( "%s.%s" , mod . parent . fullyQualifiedImportName ( ) , name )
}
2020-01-21 22:45:48 +00:00
// genInit emits an __init__.py module, optionally re-exporting other members or submodules.
func ( mod * modContext ) genInit ( exports [ ] string ) string {
w := & bytes . Buffer { }
2020-09-11 21:27:36 +00:00
mod . genHeader ( w , false /*needsSDK*/ , nil )
2021-07-14 21:26:50 +00:00
if mod . isConfig {
fmt . Fprintf ( w , "import sys\n" )
fmt . Fprintf ( w , "from .vars import _ExportableConfig\n" )
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , "sys.modules[__name__].__class__ = _ExportableConfig\n" )
return w . String ( )
}
2021-04-29 20:08:22 +00:00
fmt . Fprintf ( w , "%s\n" , mod . genUtilitiesImport ( ) )
fmt . Fprintf ( w , "import typing\n" )
2020-01-21 22:45:48 +00:00
2020-06-24 18:41:03 +00:00
// Import anything to export flatly that is a direct export rather than sub-module.
2020-01-21 22:45:48 +00:00
if len ( exports ) > 0 {
sort . Slice ( exports , func ( i , j int ) bool {
return PyName ( exports [ i ] ) < PyName ( exports [ j ] )
} )
fmt . Fprintf ( w , "# Export this package's modules as members:\n" )
for _ , exp := range exports {
2020-06-23 20:02:22 +00:00
name := PyName ( exp )
if mod . compatibility == kubernetes20 {
// To maintain backward compatibility for kubernetes, the file names
// need to be CamelCase instead of the standard snake_case.
name = exp
}
fmt . Fprintf ( w , "from .%s import *\n" , name )
2020-01-21 22:45:48 +00:00
}
}
2020-08-19 08:16:47 +00:00
if mod . hasTypes ( true /*input*/ ) {
fmt . Fprintf ( w , "from ._inputs import *\n" )
}
if mod . hasTypes ( false /*input*/ ) {
fmt . Fprintf ( w , "from . import outputs\n" )
}
2020-01-21 22:45:48 +00:00
2020-06-24 18:41:03 +00:00
// If there are subpackages, import them with importlib.
if mod . submodulesExist ( ) {
2021-04-29 20:08:22 +00:00
children := make ( [ ] * modContext , len ( mod . children ) )
copy ( children , mod . children )
sort . Slice ( children , func ( i , j int ) bool {
return PyName ( children [ i ] . mod ) < PyName ( children [ j ] . mod )
2020-06-24 18:41:03 +00:00
} )
fmt . Fprintf ( w , "\n# Make subpackages available:\n" )
2022-07-29 15:07:27 +00:00
2021-04-29 20:08:22 +00:00
fmt . Fprintf ( w , "if typing.TYPE_CHECKING:\n" )
for _ , submod := range children {
if ! submod . isEmpty ( ) {
2021-07-30 20:35:02 +00:00
unq := submod . unqualifiedImportName ( )
// The `__iam = iam` hack enables
// PyCharm and VSCode completion to do
// better.
//
// See https://github.com/pulumi/pulumi/issues/7367
fmt . Fprintf ( w , " import %s as __%s\n %s = __%s\n" ,
2021-04-29 20:08:22 +00:00
submod . fullyQualifiedImportName ( ) ,
2021-07-30 20:35:02 +00:00
unq ,
unq ,
unq )
2020-08-19 08:16:47 +00:00
}
2021-04-29 20:08:22 +00:00
}
2020-08-19 08:16:47 +00:00
2021-04-29 20:08:22 +00:00
fmt . Fprintf ( w , "else:\n" )
for _ , submod := range children {
if ! submod . isEmpty ( ) {
fmt . Fprintf ( w , " %s = _utilities.lazy_import('%s')\n" ,
submod . unqualifiedImportName ( ) ,
submod . fullyQualifiedImportName ( ) )
2020-06-24 18:41:03 +00:00
}
}
2021-04-29 20:08:22 +00:00
fmt . Fprintf ( w , "\n" )
2020-06-24 18:41:03 +00:00
}
2020-11-24 20:43:23 +00:00
// If there are resources in this module, register the module with the runtime.
if len ( mod . resources ) != 0 {
2021-04-29 20:08:22 +00:00
err := genResourceMappings ( mod , w )
2023-02-21 23:27:34 +00:00
contract . AssertNoErrorf ( err , "error generating resource mappings" )
2020-11-24 20:43:23 +00:00
}
2020-01-21 22:45:48 +00:00
return w . String ( )
}
2024-01-05 09:05:41 +00:00
func ( mod * modContext ) getRelImportFromRoot ( target string ) string {
rel , err := filepath . Rel ( mod . mod , target )
2023-02-21 23:27:34 +00:00
contract . AssertNoErrorf ( err , "error turning %q into a relative path" , mod . mod )
2024-01-05 09:05:41 +00:00
if path . Base ( rel ) == "." {
rel = path . Dir ( rel )
}
return relPathToRelImport ( rel )
2020-11-25 05:43:32 +00:00
}
2021-04-29 20:08:22 +00:00
func ( mod * modContext ) genUtilitiesImport ( ) string {
2020-12-02 21:45:25 +00:00
rel , err := filepath . Rel ( mod . mod , "" )
2023-02-21 23:27:34 +00:00
contract . AssertNoErrorf ( err , "error turning %q into a relative path" , mod . mod )
2020-12-02 21:45:25 +00:00
relRoot := path . Dir ( rel )
relImport := relPathToRelImport ( relRoot )
2021-04-29 20:08:22 +00:00
return fmt . Sprintf ( "from %s import _utilities" , relImport )
2020-11-24 20:43:23 +00:00
}
2021-02-12 23:54:19 +00:00
func ( mod * modContext ) importObjectType ( t * schema . ObjectType , input bool ) string {
2022-12-07 12:03:41 +00:00
if ! codegen . PkgEquals ( t . PackageReference , mod . pkg ) {
2023-12-12 12:19:42 +00:00
return "import " + pyPack ( t . PackageReference . Name ( ) )
2021-02-12 23:54:19 +00:00
}
tok := t . Token
2020-11-19 20:56:28 +00:00
parts := strings . Split ( tok , ":" )
2023-02-21 23:27:34 +00:00
contract . Assertf ( len ( parts ) == 3 , "type token %q is not in the form '<pkg>:<mod>:<type>'" , tok )
2020-11-19 20:56:28 +00:00
2020-08-19 08:16:47 +00:00
modName := mod . tokenToModule ( tok )
if modName == mod . mod {
if input {
return "from ._inputs import *"
}
return "from . import outputs"
}
2024-01-05 09:05:41 +00:00
importPath := mod . getRelImportFromRoot ( "" )
2020-08-19 08:16:47 +00:00
if modName == "" {
imp , as := "outputs" , "_root_outputs"
if input {
imp , as = "_inputs" , "_root_inputs"
}
2020-11-19 20:56:28 +00:00
return fmt . Sprintf ( "from %s import %s as %s" , importPath , imp , as )
2020-08-19 08:16:47 +00:00
}
components := strings . Split ( modName , "/" )
2020-11-19 20:56:28 +00:00
return fmt . Sprintf ( "from %s import %[2]s as _%[2]s" , importPath , components [ 0 ] )
2020-08-19 08:16:47 +00:00
}
2022-05-12 08:59:56 +00:00
func ( mod * modContext ) importEnumType ( e * schema . EnumType ) string {
2022-12-07 12:03:41 +00:00
if ! codegen . PkgEquals ( e . PackageReference , mod . pkg ) {
2023-12-12 12:19:42 +00:00
return "import " + pyPack ( e . PackageReference . Name ( ) )
2022-05-12 08:59:56 +00:00
}
modName := mod . tokenToModule ( e . Token )
2020-11-25 05:43:32 +00:00
if modName == mod . mod {
return "from ._enums import *"
}
2024-01-05 09:05:41 +00:00
importPath := mod . getRelImportFromRoot ( "" )
2020-11-25 05:43:32 +00:00
if modName == "" {
return fmt . Sprintf ( "from %s import _enums as _root_enums" , importPath )
}
components := strings . Split ( modName , "/" )
return fmt . Sprintf ( "from %s import %s" , importPath , components [ 0 ] )
}
2021-02-12 23:54:19 +00:00
func ( mod * modContext ) importResourceType ( r * schema . ResourceType ) string {
2022-12-07 12:03:41 +00:00
if r . Resource != nil && ! codegen . PkgEquals ( r . Resource . PackageReference , mod . pkg ) {
2023-12-12 12:19:42 +00:00
return "import " + pyPack ( r . Resource . PackageReference . Name ( ) )
2021-02-12 23:54:19 +00:00
}
tok := r . Token
2020-11-19 20:56:28 +00:00
parts := strings . Split ( tok , ":" )
2023-02-21 23:27:34 +00:00
contract . Assertf ( len ( parts ) == 3 , "type token %q is not in the form '<pkg>:<mod>:<type>'" , tok )
2020-11-23 20:28:00 +00:00
// If it's a provider resource, import the top-level package.
if parts [ 0 ] == "pulumi" && parts [ 1 ] == "providers" {
2023-12-12 12:19:42 +00:00
return "import pulumi_" + parts [ 2 ]
2020-11-23 20:28:00 +00:00
}
2024-01-05 09:05:41 +00:00
modName := mod . tokenToModule ( tok )
if mod . mod == modName || modName == "" {
// We want a relative import if we're in the same module: from .some_member import SomeMember
importPath := mod . getRelImportFromRoot ( modName )
2020-09-23 19:39:25 +00:00
2024-01-05 09:05:41 +00:00
name := PyName ( tokenToName ( r . Token ) )
if mod . compatibility == kubernetes20 {
// To maintain backward compatibility for kubernetes, the file names
// need to be CamelCase instead of the standard snake_case.
name = tokenToName ( r . Token )
}
if r . Resource != nil && r . Resource . IsProvider {
name = "provider"
}
2020-09-23 19:39:25 +00:00
2024-01-05 09:05:41 +00:00
if strings . HasSuffix ( importPath , "." ) {
importPath += name
} else {
importPath = importPath + "." + name
}
resourceName := mod . tokenToResource ( tok )
return fmt . Sprintf ( "from %s import %s" , importPath , resourceName )
2021-02-12 23:54:19 +00:00
}
2020-09-23 19:39:25 +00:00
components := strings . Split ( modName , "/" )
2024-01-05 09:05:41 +00:00
return fmt . Sprintf ( "from %s import %[2]s as _%[2]s" , mod . getRelImportFromRoot ( "" ) , components [ 0 ] )
2020-09-23 19:39:25 +00:00
}
2021-07-14 21:26:50 +00:00
// genConfig emits all config variables in the given module, returning the resulting file.
2020-01-21 22:45:48 +00:00
func ( mod * modContext ) genConfig ( variables [ ] * schema . Property ) ( string , error ) {
w := & bytes . Buffer { }
2020-08-19 08:16:47 +00:00
2021-04-19 23:40:39 +00:00
imports := imports { }
mod . collectImports ( variables , imports , false /*input*/ )
2020-08-19 08:16:47 +00:00
2020-09-11 21:27:36 +00:00
mod . genHeader ( w , true /*needsSDK*/ , imports )
2021-07-14 21:26:50 +00:00
fmt . Fprintf ( w , "import types\n" )
fmt . Fprintf ( w , "\n" )
2020-01-21 22:45:48 +00:00
// Create a config bag for the variables to pull from.
2022-12-07 12:03:41 +00:00
fmt . Fprintf ( w , "__config__ = pulumi.Config('%s')\n" , mod . pkg . Name ( ) )
2021-07-14 21:26:50 +00:00
fmt . Fprintf ( w , "\n\n" )
// To avoid a breaking change to the existing config getters, we define a class that extends
// the `ModuleType` type and implements property getters for each config key. We then overwrite
// the `__class__` attribute of the current module as described in the proposal for PEP-549. This allows
// us to maintain the existing interface for users but implement dynamic getters behind the scenes.
fmt . Fprintf ( w , "class _ExportableConfig(types.ModuleType):\n" )
indent := " "
2020-01-21 22:45:48 +00:00
// Emit an entry for all config variables.
for _ , p := range variables {
2021-07-16 16:48:03 +00:00
configFetch , err := genConfigFetch ( p )
if err != nil {
return "" , err
2021-07-14 21:26:50 +00:00
}
2021-07-16 16:48:03 +00:00
typeString := genConfigVarType ( p )
2021-07-14 21:26:50 +00:00
fmt . Fprintf ( w , "%s@property\n" , indent )
fmt . Fprintf ( w , "%sdef %s(self) -> %s:\n" , indent , PyName ( p . Name ) , typeString )
dblIndent := strings . Repeat ( indent , 2 )
printComment ( w , p . Comment , dblIndent )
fmt . Fprintf ( w , "%sreturn %s\n" , dblIndent , configFetch )
fmt . Fprintf ( w , "\n" )
}
return w . String ( ) , nil
}
2021-07-16 16:48:03 +00:00
func genConfigFetch ( configVar * schema . Property ) ( string , error ) {
getFunc := "get"
unwrappedType := codegen . UnwrapType ( configVar . Type )
switch unwrappedType {
case schema . BoolType :
getFunc = "get_bool"
case schema . IntType :
getFunc = "get_int"
case schema . NumberType :
getFunc = "get_float"
}
configFetch := fmt . Sprintf ( "__config__.%s('%s')" , getFunc , configVar . Name )
if configVar . DefaultValue != nil {
v , err := getDefaultValue ( configVar . DefaultValue , unwrappedType )
if err != nil {
return "" , err
}
configFetch += " or " + v
}
return configFetch , nil
}
func genConfigVarType ( configVar * schema . Property ) string {
// For historical reasons and to maintain backwards compatibility, the config variables for python
// are typed as `Optional[str`] or `str` for complex objects since the getters only use config.get().
// To return the rich objects would be a breaking change, tracked in https://github.com/pulumi/pulumi/issues/7493
typeString := "str"
switch codegen . UnwrapType ( configVar . Type ) {
case schema . BoolType :
typeString = "bool"
case schema . IntType :
typeString = "int"
case schema . NumberType :
typeString = "float"
}
if configVar . DefaultValue == nil || configVar . DefaultValue . Value == nil {
typeString = "Optional[" + typeString + "]"
}
return typeString
}
2021-07-14 21:26:50 +00:00
// genConfigStubs emits all type information for the config variables in the given module, returning the resulting file.
// We do this because we lose IDE autocomplete by implementing the dynamic config getters described in genConfig.
// Emitting these stubs allows us to maintain type hints and autocomplete for users.
func ( mod * modContext ) genConfigStubs ( variables [ ] * schema . Property ) ( string , error ) {
w := & bytes . Buffer { }
imports := imports { }
mod . collectImports ( variables , imports , false /*input*/ )
mod . genHeader ( w , true /*needsSDK*/ , imports )
// Emit an entry for all config variables.
for _ , p := range variables {
2021-07-16 16:48:03 +00:00
typeString := genConfigVarType ( p )
2021-07-14 21:26:50 +00:00
fmt . Fprintf ( w , "%s: %s\n" , p . Name , typeString )
2020-01-21 22:45:48 +00:00
printComment ( w , p . Comment , "" )
fmt . Fprintf ( w , "\n" )
}
return w . String ( ) , nil
}
2021-11-12 00:00:03 +00:00
func allTypesAreOverlays ( types [ ] * schema . ObjectType ) bool {
for _ , t := range types {
if ! t . IsOverlay {
return false
}
}
return true
}
2022-11-09 19:49:59 +00:00
func ( mod * modContext ) genTypes ( dir string , fs codegen . Fs ) error {
2020-08-19 08:16:47 +00:00
genTypes := func ( file string , input bool ) error {
w := & bytes . Buffer { }
2021-11-12 00:00:03 +00:00
if allTypesAreOverlays ( mod . types ) {
// If all resources in this module are overlays, skip further code generation.
return nil
}
2021-04-19 23:40:39 +00:00
imports := imports { }
2020-08-19 08:16:47 +00:00
for _ , t := range mod . types {
2021-11-12 00:00:03 +00:00
if t . IsOverlay {
// This type is generated by the provider, so no further action is required.
continue
}
2020-08-19 08:16:47 +00:00
if input && mod . details ( t ) . inputType {
2021-06-24 16:17:55 +00:00
visitObjectTypes ( t . Properties , func ( t schema . Type ) {
2021-04-19 23:40:39 +00:00
switch t := t . ( type ) {
2020-09-23 19:39:25 +00:00
case * schema . ObjectType :
2021-04-19 23:40:39 +00:00
imports . addTypeIf ( mod , t , true /*input*/ , func ( imp string ) bool {
2020-09-23 19:39:25 +00:00
// No need to import `._inputs` inside _inputs.py.
return imp != "from ._inputs import *"
} )
2020-11-25 05:43:32 +00:00
case * schema . EnumType :
2022-05-12 08:59:56 +00:00
imports . addEnum ( mod , t )
2020-09-23 19:39:25 +00:00
case * schema . ResourceType :
2021-04-19 23:40:39 +00:00
imports . addResource ( mod , t )
2020-09-23 19:39:25 +00:00
}
2020-08-19 08:16:47 +00:00
} )
}
if ! input && mod . details ( t ) . outputType {
2021-04-19 23:40:39 +00:00
mod . collectImports ( t . Properties , imports , false /*input*/ )
2020-08-19 08:16:47 +00:00
}
}
2020-11-25 05:43:32 +00:00
for _ , e := range mod . enums {
2022-05-12 08:59:56 +00:00
imports . addEnum ( mod , e )
2020-11-25 05:43:32 +00:00
}
2020-08-19 08:16:47 +00:00
2020-09-11 21:27:36 +00:00
mod . genHeader ( w , true /*needsSDK*/ , imports )
2020-08-19 08:16:47 +00:00
// Export only the symbols we want exported.
fmt . Fprintf ( w , "__all__ = [\n" )
for _ , t := range mod . types {
2021-11-12 00:00:03 +00:00
if t . IsOverlay {
// This type is generated by the provider, so no further action is required.
continue
}
2021-06-24 16:17:55 +00:00
if input && mod . details ( t ) . inputType || ! input && mod . details ( t ) . outputType {
fmt . Fprintf ( w , " '%s',\n" , mod . unqualifiedObjectTypeName ( t , input ) )
2020-08-19 08:16:47 +00:00
}
2024-06-18 15:24:42 +00:00
if input && typedDictEnabled ( mod . inputTypes ) && mod . details ( t ) . inputType {
fmt . Fprintf ( w , " '%sDict',\n" , mod . unqualifiedObjectTypeName ( t , input ) )
}
2020-08-19 08:16:47 +00:00
}
fmt . Fprintf ( w , "]\n\n" )
2024-06-18 15:24:42 +00:00
if input && typedDictEnabled ( mod . inputTypes ) {
fmt . Fprintf ( w , "MYPY = False\n\n" )
}
2020-08-19 08:16:47 +00:00
var hasTypes bool
for _ , t := range mod . types {
2021-11-12 00:00:03 +00:00
if t . IsOverlay {
// This type is generated by the provider, so no further action is required.
continue
}
2020-08-19 08:16:47 +00:00
if input && mod . details ( t ) . inputType {
2021-04-16 02:03:28 +00:00
if err := mod . genObjectType ( w , t , true ) ; err != nil {
2020-08-19 08:16:47 +00:00
return err
}
hasTypes = true
}
if ! input && mod . details ( t ) . outputType {
2021-04-16 02:03:28 +00:00
if err := mod . genObjectType ( w , t , false ) ; err != nil {
2020-08-19 08:16:47 +00:00
return err
}
hasTypes = true
}
}
if hasTypes {
2022-11-09 19:49:59 +00:00
fs . Add ( path . Join ( dir , file ) , w . Bytes ( ) )
2020-08-19 08:16:47 +00:00
}
return nil
}
if err := genTypes ( "_inputs.py" , true ) ; err != nil {
return err
}
2021-09-21 17:00:44 +00:00
return genTypes ( "outputs.py" , false )
2020-08-19 08:16:47 +00:00
}
func awaitableTypeNames ( tok string ) ( baseName , awaitableName string ) {
baseName = pyClassName ( tokenToName ( tok ) )
awaitableName = "Awaitable" + baseName
return
}
2020-01-21 22:45:48 +00:00
func ( mod * modContext ) genAwaitableType ( w io . Writer , obj * schema . ObjectType ) string {
2020-08-19 08:16:47 +00:00
baseName , awaitableName := awaitableTypeNames ( obj . Token )
2020-01-21 22:45:48 +00:00
// Produce a class definition with optional """ comment.
2020-08-19 08:16:47 +00:00
fmt . Fprint ( w , "@pulumi.output_type\n" )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , "class %s:\n" , baseName )
printComment ( w , obj . Comment , " " )
// Now generate an initializer with properties for all inputs.
fmt . Fprintf ( w , " def __init__(__self__" )
for _ , prop := range obj . Properties {
fmt . Fprintf ( w , ", %s=None" , PyName ( prop . Name ) )
}
fmt . Fprintf ( w , "):\n" )
2022-05-23 21:43:38 +00:00
if len ( obj . Properties ) == 0 {
fmt . Fprintf ( w , " pass" )
}
2020-01-21 22:45:48 +00:00
for _ , prop := range obj . Properties {
// Check that required arguments are present. Also check that types are as expected.
pname := PyName ( prop . Name )
2020-09-23 19:39:25 +00:00
ptype := mod . pyType ( prop . Type )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " if %s and not isinstance(%s, %s):\n" , pname , pname , ptype )
fmt . Fprintf ( w , " raise TypeError(\"Expected argument '%s' to be a %s\")\n" , pname , ptype )
2020-08-19 08:16:47 +00:00
// Now perform the assignment.
fmt . Fprintf ( w , " pulumi.set(__self__, \"%[1]s\", %[1]s)\n" , pname )
2020-01-21 22:45:48 +00:00
}
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , "\n" )
2020-01-21 22:45:48 +00:00
2020-08-19 08:16:47 +00:00
// Write out Python property getters for each property.
2023-06-20 15:44:16 +00:00
// Note that deprecation messages will be emitted on access to the property, rather than initialization.
// This avoids spamming end users with irrelevant deprecation messages.
2021-07-07 13:57:18 +00:00
mod . genProperties ( w , obj . Properties , false /*setters*/ , "" , func ( prop * schema . Property ) string {
2024-06-18 15:24:42 +00:00
return mod . typeString ( prop . Type , false /*input*/ , false /*acceptMapping*/ , false /*forDict*/ )
2020-08-28 15:55:02 +00:00
} )
2020-01-21 22:45:48 +00:00
// Produce an awaitable subclass.
2020-08-21 03:51:32 +00:00
fmt . Fprint ( w , "\n" )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , "class %s(%s):\n" , awaitableName , baseName )
// Emit __await__ and __iter__ in order to make this type awaitable.
//
// Note that we need __await__ to be an iterator, but we only want it to return one value. As such, we use
// `if False: yield` to construct this.
//
// We also need the result of __await__ to be a plain, non-awaitable value. We achieve this by returning a new
// instance of the base class.
fmt . Fprintf ( w , " # pylint: disable=using-constant-test\n" )
fmt . Fprintf ( w , " def __await__(self):\n" )
fmt . Fprintf ( w , " if False:\n" )
fmt . Fprintf ( w , " yield self\n" )
2022-05-23 21:43:38 +00:00
fmt . Fprintf ( w , " return %s(" , baseName )
2020-01-21 22:45:48 +00:00
for i , prop := range obj . Properties {
if i > 0 {
2022-05-23 21:43:38 +00:00
fmt . Fprintf ( w , "," )
2020-01-21 22:45:48 +00:00
}
pname := PyName ( prop . Name )
2022-05-23 21:43:38 +00:00
fmt . Fprintf ( w , "\n %s=self.%s" , pname , pname )
2020-01-21 22:45:48 +00:00
}
fmt . Fprintf ( w , ")\n" )
return awaitableName
}
2021-07-07 13:57:18 +00:00
func resourceName ( res * schema . Resource ) string {
name := pyClassName ( tokenToName ( res . Token ) )
if res . IsProvider {
name = "Provider"
}
return name
}
2020-01-21 22:45:48 +00:00
func ( mod * modContext ) genResource ( res * schema . Resource ) ( string , error ) {
w := & bytes . Buffer { }
2020-08-19 08:16:47 +00:00
2021-04-19 23:40:39 +00:00
imports := imports { }
2021-07-07 13:57:18 +00:00
mod . collectImportsForResource ( res . Properties , imports , false /*input*/ , res )
mod . collectImportsForResource ( res . InputProperties , imports , true /*input*/ , res )
2020-08-19 08:16:47 +00:00
if res . StateInputs != nil {
2021-07-07 13:57:18 +00:00
mod . collectImportsForResource ( res . StateInputs . Properties , imports , true /*input*/ , res )
}
for _ , method := range res . Methods {
if method . Function . Inputs != nil {
mod . collectImportsForResource ( method . Function . Inputs . Properties , imports , true /*input*/ , res )
}
2023-01-11 22:17:14 +00:00
returnType := returnTypeObject ( method . Function )
if returnType != nil {
mod . collectImportsForResource ( returnType . Properties , imports , false /*input*/ , res )
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
} else if method . Function . ReturnTypePlain {
mod . collectImportsForResource ( [ ] * schema . Property { {
Name : "res" ,
Type : method . Function . ReturnType ,
Plain : true ,
} } , imports , false /*input*/ , res )
2021-07-07 13:57:18 +00:00
}
2020-08-19 08:16:47 +00:00
}
2020-09-11 21:27:36 +00:00
mod . genHeader ( w , true /*needsSDK*/ , imports )
2020-08-19 08:16:47 +00:00
2021-07-07 13:57:18 +00:00
name := resourceName ( res )
2020-08-19 08:16:47 +00:00
2023-12-12 12:19:42 +00:00
resourceArgsName := name + "Args"
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
// Some providers (e.g. Kubernetes) have types with the same name as resources (e.g. StorageClass in Kubernetes).
// We've already shipped the input type (e.g. StorageClassArgs) in the same module as the resource, so we can't use
// the same name for the resource's args class. When an input type exists that would conflict with the name of the
// resource args class, we'll use a different name: `<Resource>InitArgs` instead of `<Resource>Args`.
const alternateSuffix = "InitArgs"
for _ , t := range mod . types {
if mod . details ( t ) . inputType {
2021-06-24 16:17:55 +00:00
if mod . unqualifiedObjectTypeName ( t , true ) == resourceArgsName {
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
resourceArgsName = name + alternateSuffix
break
}
}
}
// If we're using the alternate name, ensure the alternate name doesn't conflict with an input type.
if strings . HasSuffix ( resourceArgsName , alternateSuffix ) {
for _ , t := range mod . types {
if mod . details ( t ) . inputType {
2021-06-24 16:17:55 +00:00
if mod . unqualifiedObjectTypeName ( t , true ) == resourceArgsName {
2021-11-13 02:37:17 +00:00
return "" , fmt . Errorf ( "resource args class named %s in %s conflicts with input type" , resourceArgsName , mod . mod )
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
}
}
}
}
2020-08-19 08:16:47 +00:00
// Export only the symbols we want exported.
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( w , "__all__ = ['%s', '%s']\n\n" , resourceArgsName , name )
2021-04-02 17:09:17 +00:00
// Produce an args class.
argsComment := fmt . Sprintf ( "The set of arguments for constructing a %s resource." , name )
2021-06-24 16:17:55 +00:00
err := mod . genType ( w , resourceArgsName , argsComment , res . InputProperties , true , false )
2021-04-02 17:09:17 +00:00
if err != nil {
return "" , err
}
2020-01-21 22:45:48 +00:00
2021-04-07 19:35:19 +00:00
// Produce an unexported state class. It's currently only used internally inside the `get` method to opt-in to
// the type/name metadata based translation behavior.
// We can consider making use of it publicly in the future: removing the underscore prefix, exporting it from
// `__all__`, and adding a static `get` overload that accepts it as an argument.
hasStateInputs := ! res . IsProvider && ! res . IsComponent && res . StateInputs != nil &&
len ( res . StateInputs . Properties ) > 0
if hasStateInputs {
stateComment := fmt . Sprintf ( "Input properties used for looking up and filtering %s resources." , name )
2021-06-24 16:17:55 +00:00
err = mod . genType ( w , fmt . Sprintf ( "_%sState" , name ) , stateComment , res . StateInputs . Properties , true , false )
2021-04-07 19:35:19 +00:00
if err != nil {
return "" , err
}
}
2020-09-24 00:23:46 +00:00
var baseType string
switch {
case res . IsProvider :
2020-01-21 22:45:48 +00:00
baseType = "pulumi.ProviderResource"
2020-09-24 00:23:46 +00:00
case res . IsComponent :
baseType = "pulumi.ComponentResource"
default :
baseType = "pulumi.CustomResource"
2020-01-21 22:45:48 +00:00
}
2020-07-01 19:50:26 +00:00
if ! res . IsProvider && res . DeprecationMessage != "" && mod . compatibility != kubernetes20 {
2020-06-17 17:14:11 +00:00
escaped := strings . ReplaceAll ( res . DeprecationMessage , ` " ` , ` \" ` )
2021-04-02 17:09:17 +00:00
fmt . Fprintf ( w , "warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n\n" , escaped )
2020-01-21 22:45:48 +00:00
}
// Produce a class definition with optional """ comment.
2020-07-07 18:32:48 +00:00
fmt . Fprintf ( w , "class %s(%s):\n" , name , baseType )
2020-07-01 19:50:26 +00:00
if res . DeprecationMessage != "" && mod . compatibility != kubernetes20 {
2020-06-17 17:14:11 +00:00
escaped := strings . ReplaceAll ( res . DeprecationMessage , ` " ` , ` \" ` )
2020-11-09 14:28:28 +00:00
fmt . Fprintf ( w , " warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n" , escaped )
2020-01-21 22:45:48 +00:00
}
2021-04-02 17:09:17 +00:00
// Determine if all inputs are optional.
allOptionalInputs := true
2020-01-21 22:45:48 +00:00
for _ , prop := range res . InputProperties {
2021-06-24 16:17:55 +00:00
allOptionalInputs = allOptionalInputs && ! prop . IsRequired ( )
2020-01-21 22:45:48 +00:00
}
2021-04-02 17:09:17 +00:00
// Emit __init__ overloads and implementation...
// Helper for generating an init method with inputs as function arguments.
emitInitMethodSignature := func ( methodName string ) {
fmt . Fprintf ( w , " def %s(__self__,\n" , methodName )
fmt . Fprintf ( w , " resource_name: str,\n" )
fmt . Fprintf ( w , " opts: Optional[pulumi.ResourceOptions] = None" )
// If there's an argument type, emit it.
for _ , prop := range res . InputProperties {
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( codegen . OptionalType ( prop ) , true , true /*acceptMapping*/ , false /*forDict*/ )
2021-04-02 17:09:17 +00:00
fmt . Fprintf ( w , ",\n %s: %s = None" , InitParamName ( prop . Name ) , ty )
}
2021-04-14 09:21:16 +00:00
fmt . Fprintf ( w , ",\n __props__=None):\n" )
2021-04-02 17:09:17 +00:00
}
// Emit an __init__ overload that accepts the resource's inputs as function arguments.
fmt . Fprintf ( w , " @overload\n" )
emitInitMethodSignature ( "__init__" )
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
mod . genInitDocstring ( w , res , resourceArgsName , false /*argsOverload*/ )
2021-04-02 17:09:17 +00:00
fmt . Fprintf ( w , " ...\n" )
// Emit an __init__ overload that accepts the resource's inputs from the args class.
fmt . Fprintf ( w , " @overload\n" )
fmt . Fprintf ( w , " def __init__(__self__,\n" )
fmt . Fprintf ( w , " resource_name: str,\n" )
if allOptionalInputs {
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( w , " args: Optional[%s] = None,\n" , resourceArgsName )
2021-04-02 17:09:17 +00:00
} else {
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( w , " args: %s,\n" , resourceArgsName )
2021-04-02 17:09:17 +00:00
}
fmt . Fprintf ( w , " opts: Optional[pulumi.ResourceOptions] = None):\n" )
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
mod . genInitDocstring ( w , res , resourceArgsName , true /*argsOverload*/ )
2021-04-02 17:09:17 +00:00
fmt . Fprintf ( w , " ...\n" )
// Emit the actual implementation of __init__, which does the appropriate thing based on which
// overload was called.
fmt . Fprintf ( w , " def __init__(__self__, resource_name: str, *args, **kwargs):\n" )
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( w , " resource_args, opts = _utilities.get_resource_args_opts(%s, pulumi.ResourceOptions, *args, **kwargs)\n" , resourceArgsName )
2021-04-02 17:09:17 +00:00
fmt . Fprintf ( w , " if resource_args is not None:\n" )
fmt . Fprintf ( w , " __self__._internal_init(resource_name, opts, **resource_args.__dict__)\n" )
fmt . Fprintf ( w , " else:\n" )
fmt . Fprintf ( w , " __self__._internal_init(resource_name, *args, **kwargs)\n" )
fmt . Fprintf ( w , "\n" )
// Emit the _internal_init helper method which provides the bulk of the __init__ implementation.
emitInitMethodSignature ( "_internal_init" )
2020-07-01 19:50:26 +00:00
if res . DeprecationMessage != "" && mod . compatibility != kubernetes20 {
2021-02-26 05:22:04 +00:00
fmt . Fprintf ( w , " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n" , name , res . DeprecationMessage )
2020-01-21 22:45:48 +00:00
}
2022-06-14 23:52:24 +00:00
fmt . Fprintf ( w , " opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts)\n" )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " if not isinstance(opts, pulumi.ResourceOptions):\n" )
fmt . Fprintf ( w , " raise TypeError('Expected resource options to be a ResourceOptions instance')\n" )
2020-09-30 16:16:25 +00:00
if res . IsComponent {
fmt . Fprintf ( w , " if opts.id is not None:\n" )
fmt . Fprintf ( w , " raise ValueError('ComponentResource classes do not support opts.id')\n" )
fmt . Fprintf ( w , " else:\n" )
} else {
fmt . Fprintf ( w , " if opts.id is None:\n" )
}
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " if __props__ is not None:\n" )
fmt . Fprintf ( w , " raise TypeError(" )
fmt . Fprintf ( w , "'__props__ is only valid when passed in combination with a valid opts.id to get an existing resource')\n" )
2021-04-07 19:35:19 +00:00
// We use an instance of the `<Resource>Args` class for `__props__` to opt-in to the type/name metadata based
// translation behavior. The instance is created using `__new__` to avoid any validation in the `__init__` method,
// values are set directly on its `__dict__`, including any additional output properties.
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( w , " __props__ = %[1]s.__new__(%[1]s)\n\n" , resourceArgsName )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , "" )
2021-03-01 17:54:25 +00:00
ins := codegen . NewStringSet ( )
2020-01-21 22:45:48 +00:00
for _ , prop := range res . InputProperties {
2020-08-19 15:34:06 +00:00
pname := InitParamName ( prop . Name )
2020-06-22 17:58:13 +00:00
var arg interface { }
var err error
2020-01-21 22:45:48 +00:00
// Fill in computed defaults for arguments.
if prop . DefaultValue != nil {
2021-06-24 16:17:55 +00:00
dv , err := getDefaultValue ( prop . DefaultValue , codegen . UnwrapType ( prop . Type ) )
2020-01-21 22:45:48 +00:00
if err != nil {
return "" , err
}
fmt . Fprintf ( w , " if %s is None:\n" , pname )
fmt . Fprintf ( w , " %s = %s\n" , pname , dv )
}
// Check that required arguments are present.
2021-06-24 16:17:55 +00:00
if prop . IsRequired ( ) {
2020-11-24 20:43:23 +00:00
fmt . Fprintf ( w , " if %s is None and not opts.urn:\n" , pname )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " raise TypeError(\"Missing required property '%s'\")\n" , pname )
}
// And add it to the dictionary.
2020-06-22 17:58:13 +00:00
arg = pname
if prop . ConstValue != nil {
arg , err = getConstValue ( prop . ConstValue )
if err != nil {
return "" , err
}
}
2020-01-21 22:45:48 +00:00
// If this resource is a provider then, regardless of the schema of the underlying provider
// type, we must project all properties as strings. For all properties that are not strings,
// we'll marshal them to JSON and use the JSON string as a string input.
2022-11-30 06:53:16 +00:00
handledSecret := false
2020-02-07 17:43:20 +00:00
if res . IsProvider && ! isStringType ( prop . Type ) {
2022-11-30 06:53:16 +00:00
if prop . Secret {
arg = fmt . Sprintf ( "pulumi.Output.secret(%s).apply(pulumi.runtime.to_json) if %s is not None else None" , arg , arg )
handledSecret = true
} else {
arg = fmt . Sprintf ( "pulumi.Output.from_input(%s).apply(pulumi.runtime.to_json) if %s is not None else None" , arg , arg )
}
2020-01-21 22:45:48 +00:00
}
2021-05-26 22:00:51 +00:00
name := PyName ( prop . Name )
2022-11-30 06:53:16 +00:00
if prop . Secret && ! handledSecret {
2021-05-26 22:00:51 +00:00
fmt . Fprintf ( w , " __props__.__dict__[%[1]q] = None if %[2]s is None else pulumi.Output.secret(%[2]s)\n" , name , arg )
} else {
fmt . Fprintf ( w , " __props__.__dict__[%q] = %s\n" , name , arg )
}
2020-01-21 22:45:48 +00:00
2021-03-01 17:54:25 +00:00
ins . Add ( prop . Name )
2020-01-21 22:45:48 +00:00
}
2020-07-02 19:30:10 +00:00
var secretProps [ ] string
2020-01-21 22:45:48 +00:00
for _ , prop := range res . Properties {
// Default any pure output properties to None. This ensures they are available as properties, even if
// they don't ever get assigned a real value, and get documentation if available.
2021-03-01 17:54:25 +00:00
if ! ins . Has ( prop . Name ) {
2021-04-07 19:35:19 +00:00
fmt . Fprintf ( w , " __props__.__dict__[%q] = None\n" , PyName ( prop . Name ) )
2020-01-21 22:45:48 +00:00
}
2020-07-02 19:30:10 +00:00
if prop . Secret {
secretProps = append ( secretProps , prop . Name )
}
2020-01-21 22:45:48 +00:00
}
if len ( res . Aliases ) > 0 {
fmt . Fprintf ( w , ` alias_opts = pulumi.ResourceOptions(aliases=[ ` )
for i , alias := range res . Aliases {
2023-11-21 22:40:14 +00:00
if alias . Type != nil {
fmt . Fprintf ( w , "pulumi.Alias(type_=\"%v\")" , * alias . Type )
if i != len ( res . Aliases ) - 1 {
fmt . Fprintf ( w , ", " )
}
2020-01-21 22:45:48 +00:00
}
}
fmt . Fprintf ( w , "])\n" )
fmt . Fprintf ( w , " opts = pulumi.ResourceOptions.merge(opts, alias_opts)\n" )
}
2020-07-02 19:30:10 +00:00
if len ( secretProps ) > 0 {
2021-05-26 22:00:51 +00:00
fmt . Fprintf ( w , ` secret_opts = pulumi.ResourceOptions(additional_secret_outputs=["%s"]) ` , strings . Join ( secretProps , ` ", " ` ) )
fmt . Fprintf ( w , "\n opts = pulumi.ResourceOptions.merge(opts, secret_opts)\n" )
2020-07-02 19:30:10 +00:00
}
2021-09-08 05:23:30 +00:00
replaceOnChangesProps , errList := res . ReplaceOnChanges ( )
for _ , err := range errList {
cmdutil . Diag ( ) . Warningf ( & diag . Diag { Message : err . Error ( ) } )
}
if len ( replaceOnChangesProps ) > 0 {
[sdkgen/python] Replace-on-changes values should be camelCased (#15666)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
When replaceOnChanges is set in the schema for a property with a
multi-word name, currently, Python SDK generation incorrectly puts its
Python name into replace_on_changes.
This is wrong, because these property names are passed to the engine and
they should be engine names (i.e. schema names). All other languages
(Node.js, .NET, Go) do this correctly.
This PR adds a test case and fixes the Python generation to use the same
naming as all other SDKs.
Fixes #15665
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-13 15:43:56 +00:00
replaceOnChangesStrings := schema . PropertyListJoinToString ( replaceOnChangesProps ,
func ( x string ) string { return x } )
2021-09-08 05:23:30 +00:00
fmt . Fprintf ( w , ` replace_on_changes = pulumi.ResourceOptions(replace_on_changes=["%s"]) ` , strings . Join ( replaceOnChangesStrings , ` ", " ` ) )
fmt . Fprintf ( w , "\n opts = pulumi.ResourceOptions.merge(opts, replace_on_changes)\n" )
}
2020-01-21 22:45:48 +00:00
// Finally, chain to the base constructor, which will actually register the resource.
tok := res . Token
if res . IsProvider {
2022-12-07 12:03:41 +00:00
tok = mod . pkg . Name ( )
2020-01-21 22:45:48 +00:00
}
fmt . Fprintf ( w , " super(%s, __self__).__init__(\n" , name )
fmt . Fprintf ( w , " '%s',\n" , tok )
fmt . Fprintf ( w , " resource_name,\n" )
fmt . Fprintf ( w , " __props__,\n" )
2020-09-24 00:23:46 +00:00
if res . IsComponent {
fmt . Fprintf ( w , " opts,\n" )
2024-07-16 10:55:38 +00:00
fmt . Fprintf ( w , " remote=True" )
2020-09-24 00:23:46 +00:00
} else {
2024-07-16 10:55:38 +00:00
fmt . Fprintf ( w , " opts" )
2020-09-24 00:23:46 +00:00
}
2024-07-16 10:55:38 +00:00
pkg , err := res . PackageReference . Definition ( )
if err != nil {
return "" , err
}
if pkg . Parameterization != nil {
2024-07-29 11:23:03 +00:00
fmt . Fprintf ( w , ",\n package_ref=_utilities.get_package()" )
2024-07-16 10:55:38 +00:00
}
fmt . Fprintf ( w , ")\n\n" )
2020-01-21 22:45:48 +00:00
2020-09-30 16:16:25 +00:00
if ! res . IsProvider && ! res . IsComponent {
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " @staticmethod\n" )
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , " def get(resource_name: str,\n" )
fmt . Fprintf ( w , " id: pulumi.Input[str],\n" )
fmt . Fprintf ( w , " opts: Optional[pulumi.ResourceOptions] = None" )
2020-01-21 22:45:48 +00:00
2021-04-07 19:35:19 +00:00
if hasStateInputs {
2020-01-21 22:45:48 +00:00
for _ , prop := range res . StateInputs . Properties {
2021-06-23 11:55:23 +00:00
pname := InitParamName ( prop . Name )
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( codegen . OptionalType ( prop ) , true , true /*acceptMapping*/ , false /*forDict*/ )
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , ",\n %s: %s = None" , pname , ty )
2020-01-21 22:45:48 +00:00
}
}
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , ") -> '%s':\n" , name )
2020-01-21 22:45:48 +00:00
mod . genGetDocstring ( w , res )
fmt . Fprintf ( w ,
" opts = pulumi.ResourceOptions.merge(opts, pulumi.ResourceOptions(id=id))\n" )
fmt . Fprintf ( w , "\n" )
2021-04-07 19:35:19 +00:00
if hasStateInputs {
fmt . Fprintf ( w , " __props__ = _%[1]sState.__new__(_%[1]sState)\n\n" , name )
} else {
// If we don't have any state inputs, we'll just instantiate the `<Resource>Args` class,
// to opt-in to the improved translation behavior.
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( w , " __props__ = %[1]s.__new__(%[1]s)\n\n" , resourceArgsName )
2021-04-07 19:35:19 +00:00
}
2020-01-21 22:45:48 +00:00
2021-03-01 17:54:25 +00:00
stateInputs := codegen . NewStringSet ( )
2020-01-21 22:45:48 +00:00
if res . StateInputs != nil {
for _ , prop := range res . StateInputs . Properties {
2021-03-01 17:54:25 +00:00
stateInputs . Add ( prop . Name )
2021-06-23 11:55:23 +00:00
fmt . Fprintf ( w , " __props__.__dict__[%q] = %s\n" , PyName ( prop . Name ) , InitParamName ( prop . Name ) )
2021-03-01 17:54:25 +00:00
}
}
for _ , prop := range res . Properties {
if ! stateInputs . Has ( prop . Name ) {
2021-04-07 19:35:19 +00:00
fmt . Fprintf ( w , " __props__.__dict__[%q] = None\n" , PyName ( prop . Name ) )
2020-01-21 22:45:48 +00:00
}
}
2020-06-10 22:11:59 +00:00
fmt . Fprintf ( w , " return %s(resource_name, opts=opts, __props__=__props__)\n\n" , name )
2020-01-21 22:45:48 +00:00
}
2020-08-28 15:55:02 +00:00
// Write out Python property getters for each of the resource's properties.
2021-07-07 13:57:18 +00:00
mod . genProperties ( w , res . Properties , false /*setters*/ , "" , func ( prop * schema . Property ) string {
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( prop . Type , false /*input*/ , false /*acceptMapping*/ , false /*forDict*/ )
2020-08-28 15:55:02 +00:00
return fmt . Sprintf ( "pulumi.Output[%s]" , ty )
} )
2020-08-19 08:16:47 +00:00
2021-07-07 13:57:18 +00:00
// Write out methods.
mod . genMethods ( w , res )
2020-01-21 22:45:48 +00:00
return w . String ( ) , nil
}
2021-07-07 13:57:18 +00:00
func ( mod * modContext ) genProperties ( w io . Writer , properties [ ] * schema . Property , setters bool , indent string ,
2023-03-03 16:36:39 +00:00
propType func ( prop * schema . Property ) string ,
) {
2020-08-28 15:55:02 +00:00
// Write out Python properties for each property. If there is a property named "property", it will
// be emitted last to avoid conflicting with the built-in `@property` decorator function. We do
// this instead of importing `builtins` and fully qualifying the decorator as `@builtins.property`
// because that wouldn't address the problem if there was a property named "builtins".
emitProp := func ( pname string , prop * schema . Property ) {
ty := propType ( prop )
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "%s @property\n" , indent )
2020-08-28 15:55:02 +00:00
if pname == prop . Name {
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "%s @pulumi.getter\n" , indent )
2020-08-28 15:55:02 +00:00
} else {
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "%s @pulumi.getter(name=%q)\n" , indent , prop . Name )
2020-08-28 15:55:02 +00:00
}
Don't incorrectly emit deprecated warnings (#16400)
Presently, the Python SDK incorrectly emits deprecation warnings for
properties even if user code does not reference them. This is due to
various pieces of code in the SDK that enumerate properties e.g. for the
purpose of serialisation/RPC calls. In enumerating properties, their
getters are invoked, triggering the deprecation warnings.
There isn't currently a way for us to detect which properties may or may
not be deprecated and handle them appropriately, so this commit
introduces one. Deprecation messages are moved into a new decorator,
`@pulumi.deprecated`. This decorator accepts a `Callable` and wraps it,
making two changes:
* Before the decorated `Callable`'s execution, deprecation messages are
printed.
* The returned `Callable` is tagged with a new reserved property,
`_pulumi_deprecated_callable`, which references the wrapped `Callable`
(that is, the original `Callable` whose invocation will _not_ produce
deprecation warnings).
With this in place, we subsequently make the following two changes:
* We modify the SDK enumeration code (specifically that in
`input_type_to_dict` to check for `_pulumi_deprecated_callable`
properties and use them where appropriate to invoke getters without
triggering deprecation warnings.
* We modify Python code generation so that instead of emitting
statements to print deprecation warnings, we simply emit applications of
the `@pulumi.deprecated` decorator instead.
This commit adds some Python unit tests to exercise the decorator and
manual testing has been performed using the AWS Classic SDK.
Fixes #15894
2024-06-17 16:04:55 +00:00
if prop . DeprecationMessage != "" {
escaped := strings . ReplaceAll ( prop . DeprecationMessage , ` " ` , ` \" ` )
Generate `@deprecated` decorators in Python SDKs (#16436)
The latest version of the core Pulumi SDK contains a decorator,
`@deprecated`, that is used when generating SDK code in order to signify
deprecated properties in a way that can be recognised by other SDK code.
This is useful when writing generic Python code that e.g. traverses
class properties without triggering deprecation warnings for those not
explicitly mentioned in user code. The [original pull
request](https://github.com/pulumi/pulumi/pull/16400) has more details.
Alas, we can't rely on the fact that a user will upgrade _both_ a
particular (generated) provider SDK and the core Pulumi SDK at the same
time. Thus, it's entirely possible that a user bumps their version of
(say) `pulumi_aws`, whilst leaving their `pulumi` library at the same
(compatible, according to specified bounds) version. In doing so they'd
hit errors when the new SDK tried to import the `@deprecated` decorator,
which doesn't exist in the old core SDK.
This commit thus fixes this by altering code generation so that each SDK
receives its own inlined copy of the `@deprecated` decorator, which it
can reference regardless of the version of the core SDK. This decorator
applies the same `_pulumi_deprecated_callable` tag to functions it
decorates, which a sufficiently modern SDK will recognise to avoid
triggering e.g. https://github.com/pulumi/pulumi/issues/15894. Later on,
we can hopefully find a way to avoid doing this and use only a version
of `@deprecated` specified in the core SDK.
Codegen tests have been updated and the inlined decorator has manually
been tested using the AWS Classic SDK.
Addresses
https://github.com/pulumi/pulumi/pull/16400#discussion_r1646562455
2024-06-21 11:34:29 +00:00
fmt . Fprintf ( w , "%s @_utilities.deprecated(\"\"\"%s\"\"\")\n" , indent , escaped )
Don't incorrectly emit deprecated warnings (#16400)
Presently, the Python SDK incorrectly emits deprecation warnings for
properties even if user code does not reference them. This is due to
various pieces of code in the SDK that enumerate properties e.g. for the
purpose of serialisation/RPC calls. In enumerating properties, their
getters are invoked, triggering the deprecation warnings.
There isn't currently a way for us to detect which properties may or may
not be deprecated and handle them appropriately, so this commit
introduces one. Deprecation messages are moved into a new decorator,
`@pulumi.deprecated`. This decorator accepts a `Callable` and wraps it,
making two changes:
* Before the decorated `Callable`'s execution, deprecation messages are
printed.
* The returned `Callable` is tagged with a new reserved property,
`_pulumi_deprecated_callable`, which references the wrapped `Callable`
(that is, the original `Callable` whose invocation will _not_ produce
deprecation warnings).
With this in place, we subsequently make the following two changes:
* We modify the SDK enumeration code (specifically that in
`input_type_to_dict` to check for `_pulumi_deprecated_callable`
properties and use them where appropriate to invoke getters without
triggering deprecation warnings.
* We modify Python code generation so that instead of emitting
statements to print deprecation warnings, we simply emit applications of
the `@pulumi.deprecated` decorator instead.
This commit adds some Python unit tests to exercise the decorator and
manual testing has been performed using the AWS Classic SDK.
Fixes #15894
2024-06-17 16:04:55 +00:00
}
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "%s def %s(self) -> %s:\n" , indent , pname , ty )
2020-08-28 15:55:02 +00:00
if prop . Comment != "" {
2021-07-07 13:57:18 +00:00
printComment ( w , prop . Comment , indent + " " )
2020-08-28 15:55:02 +00:00
}
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "%s return pulumi.get(self, %q)\n\n" , indent , pname )
2020-08-28 15:55:02 +00:00
if setters {
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "%s @%s.setter\n" , indent , pname )
fmt . Fprintf ( w , "%s def %s(self, value: %s):\n" , indent , pname , ty )
fmt . Fprintf ( w , "%s pulumi.set(self, %q, value)\n\n" , indent , pname )
2020-08-28 15:55:02 +00:00
}
}
var propNamedProperty * schema . Property
for _ , prop := range properties {
pname := PyName ( prop . Name )
// If there is a property named "property", skip it, and emit it last.
if pname == "property" {
propNamedProperty = prop
continue
}
emitProp ( pname , prop )
}
if propNamedProperty != nil {
emitProp ( "property" , propNamedProperty )
}
}
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
func ( mod * modContext ) genMethodReturnType ( w io . Writer , method * schema . Method ) string {
var properties [ ] * schema . Property
var comment string
2021-07-07 13:57:18 +00:00
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
if obj := returnTypeObject ( method . Function ) ; obj != nil {
properties = obj . Properties
comment = obj . Comment
} else if method . Function . ReturnTypePlain {
comment = ""
properties = [ ] * schema . Property {
{
Name : "res" ,
Type : method . Function . ReturnType ,
Plain : true ,
} ,
2021-07-07 13:57:18 +00:00
}
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
}
2021-07-07 13:57:18 +00:00
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
name := pyClassName ( title ( method . Name ) ) + "Result"
2021-07-07 13:57:18 +00:00
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
// Produce a class definition with optional """ comment.
fmt . Fprintf ( w , " @pulumi.output_type\n" )
fmt . Fprintf ( w , " class %s:\n" , name )
printComment ( w , comment , " " )
2021-07-07 13:57:18 +00:00
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
// Now generate an initializer with properties for all inputs.
fmt . Fprintf ( w , " def __init__(__self__" )
for _ , prop := range properties {
fmt . Fprintf ( w , ", %s=None" , PyName ( prop . Name ) )
2021-07-07 13:57:18 +00:00
}
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
fmt . Fprintf ( w , "):\n" )
for _ , prop := range properties {
// Check that required arguments are present. Also check that types are as expected.
pname := PyName ( prop . Name )
ptype := mod . pyType ( prop . Type )
fmt . Fprintf ( w , " if %s and not isinstance(%s, %s):\n" , pname , pname , ptype )
fmt . Fprintf ( w , " raise TypeError(\"Expected argument '%s' to be a %s\")\n" , pname , ptype )
2021-07-07 13:57:18 +00:00
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
// Now perform the assignment.
fmt . Fprintf ( w , " pulumi.set(__self__, \"%[1]s\", %[1]s)\n" , pname )
}
fmt . Fprintf ( w , "\n" )
// Write out Python property getters for each property.
// Note that deprecation messages will be emitted on access to the property, rather than initialization.
// This avoids spamming end users with irrelevant deprecation messages.
mod . genProperties ( w , properties , false /*setters*/ , " " , func ( prop * schema . Property ) string {
2024-06-18 15:24:42 +00:00
return mod . typeString ( prop . Type , false /*input*/ , false /*acceptMapping*/ , false /*forDict*/ )
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
} )
return name
}
func ( mod * modContext ) genMethods ( w io . Writer , res * schema . Resource ) {
2021-07-07 13:57:18 +00:00
genMethod := func ( method * schema . Method ) {
methodName := PyName ( method . Name )
fun := method . Function
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
returnType := returnTypeObject ( fun )
2023-01-11 22:17:14 +00:00
shouldLiftReturn := mod . liftSingleValueMethodReturns && returnType != nil && len ( returnType . Properties ) == 1
2021-10-01 18:33:02 +00:00
2021-07-07 13:57:18 +00:00
// If there is a return type, emit it.
2021-10-01 18:33:02 +00:00
var retTypeName , retTypeNameQualified , retTypeNameQualifiedOutput , methodRetType string
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
if returnType != nil || fun . ReturnTypePlain {
retTypeName = mod . genMethodReturnType ( w , method )
2021-07-07 13:57:18 +00:00
retTypeNameQualified = fmt . Sprintf ( "%s.%s" , resourceName ( res ) , retTypeName )
retTypeNameQualifiedOutput = fmt . Sprintf ( "pulumi.Output['%s']" , retTypeNameQualified )
2021-10-01 18:33:02 +00:00
if shouldLiftReturn {
2023-01-11 22:17:14 +00:00
methodRetType = fmt . Sprintf ( "pulumi.Output['%s']" , mod . pyType ( returnType . Properties [ 0 ] . Type ) )
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
} else if fun . ReturnTypePlain {
if returnType != nil {
methodRetType = retTypeName
} else {
methodRetType = mod . pyType ( fun . ReturnType )
}
2021-10-01 18:33:02 +00:00
} else {
methodRetType = retTypeNameQualifiedOutput
}
2021-07-07 13:57:18 +00:00
}
var args [ ] * schema . Property
if fun . Inputs != nil {
// Filter out the __self__ argument from the inputs.
2023-06-28 16:02:04 +00:00
args = slice . Prealloc [ * schema . Property ] ( len ( fun . Inputs . InputShape . Properties ) - 1 )
2021-07-07 13:57:18 +00:00
for _ , arg := range fun . Inputs . InputShape . Properties {
if arg . Name == "__self__" {
continue
}
args = append ( args , arg )
}
// Sort required args first.
sort . Slice ( args , func ( i , j int ) bool {
pi , pj := args [ i ] , args [ j ]
switch {
case pi . IsRequired ( ) != pj . IsRequired ( ) :
return pi . IsRequired ( ) && ! pj . IsRequired ( )
default :
return pi . Name < pj . Name
}
} )
}
// Write out the function signature.
def := fmt . Sprintf ( " def %s(" , methodName )
var indent string
if len ( args ) > 0 {
indent = strings . Repeat ( " " , len ( def ) )
}
fmt . Fprintf ( w , "%s__self__" , def )
// Bare `*` argument to force callers to use named arguments.
if len ( args ) > 0 {
fmt . Fprintf ( w , ", *" )
}
for _ , arg := range args {
pname := PyName ( arg . Name )
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( arg . Type , true , false /*acceptMapping*/ , false /*forDict*/ )
2021-07-07 13:57:18 +00:00
var defaultValue string
if ! arg . IsRequired ( ) {
defaultValue = " = None"
}
fmt . Fprintf ( w , ",\n%s%s: %s%s" , indent , pname , ty , defaultValue )
}
if retTypeNameQualifiedOutput != "" {
2021-10-01 18:33:02 +00:00
fmt . Fprintf ( w , ") -> %s:\n" , methodRetType )
2021-07-07 13:57:18 +00:00
} else {
fmt . Fprintf ( w , ") -> None:\n" )
}
// If this func has documentation, write it at the top of the docstring, otherwise use a generic comment.
docs := & bytes . Buffer { }
if fun . Comment != "" {
fmt . Fprintln ( docs , codegen . FilterExamples ( fun . Comment , "python" ) )
}
if len ( args ) > 0 {
fmt . Fprintln ( docs , "" )
for _ , arg := range args {
mod . genPropDocstring ( docs , PyName ( arg . Name ) , arg , false /*acceptMapping*/ )
}
}
printComment ( w , docs . String ( ) , " " )
if fun . DeprecationMessage != "" {
fmt . Fprintf ( w , " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n" , methodName ,
fun . DeprecationMessage )
}
// Copy the function arguments into a dictionary.
fmt . Fprintf ( w , " __args__ = dict()\n" )
fmt . Fprintf ( w , " __args__['__self__'] = __self__\n" )
for _ , arg := range args {
pname := PyName ( arg . Name )
fmt . Fprintf ( w , " __args__['%s'] = %s\n" , arg . Name , pname )
}
// Now simply call the function with the arguments.
2024-08-09 10:43:41 +00:00
trailingArgs := ""
2021-07-07 13:57:18 +00:00
if retTypeNameQualified != "" {
// Pass along the private output_type we generated, so any nested output classes are instantiated by
// the call.
2024-08-09 10:43:41 +00:00
trailingArgs = ", typ=" + retTypeNameQualified
}
// If the call is on a parameterized package, make sure we pass the parameter.
pkg , err := fun . PackageReference . Definition ( )
contract . AssertNoErrorf ( err , "can not load package definition for %s: %s" , pkg . Name , err )
if pkg . Parameterization != nil {
trailingArgs += ", package_ref=_utilities.get_package()"
2021-07-07 13:57:18 +00:00
}
2021-10-01 18:33:02 +00:00
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
if fun . ReturnTypePlain {
property := ""
// For non-object singleton return types, unwrap the magic property "res".
if returnType == nil {
property = "." + PyName ( "res" )
}
fmt . Fprintf ( w , " return _utilities.call_plain('%s', __args__, res=__self__%s)%s\n" ,
2024-08-09 10:43:41 +00:00
fun . Token , trailingArgs , property )
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
} else if returnType == nil {
2024-08-09 10:43:41 +00:00
fmt . Fprintf ( w , " pulumi.runtime.call('%s', __args__, res=__self__%s)\n" , fun . Token , trailingArgs )
2021-10-01 18:33:02 +00:00
} else if shouldLiftReturn {
// Store the return in a variable and return the property output
2024-08-09 10:43:41 +00:00
fmt . Fprintf ( w , " __result__ = pulumi.runtime.call('%s', __args__, res=__self__%s)\n" , fun . Token , trailingArgs )
2023-01-11 22:17:14 +00:00
fmt . Fprintf ( w , " return __result__.%s\n" , PyName ( returnType . Properties [ 0 ] . Name ) )
2021-10-01 18:33:02 +00:00
} else {
// Otherwise return the call directly
2024-08-09 10:43:41 +00:00
fmt . Fprintf ( w , " return pulumi.runtime.call('%s', __args__, res=__self__%s)\n" , fun . Token , trailingArgs )
2021-10-01 18:33:02 +00:00
}
2021-07-07 13:57:18 +00:00
fmt . Fprintf ( w , "\n" )
}
for _ , method := range res . Methods {
genMethod ( method )
}
}
2020-01-21 22:45:48 +00:00
func ( mod * modContext ) genFunction ( fun * schema . Function ) ( string , error ) {
w := & bytes . Buffer { }
2021-04-19 23:40:39 +00:00
imports := imports { }
2020-08-19 08:16:47 +00:00
if fun . Inputs != nil {
2021-04-19 23:40:39 +00:00
mod . collectImports ( fun . Inputs . Properties , imports , true )
2020-08-19 08:16:47 +00:00
}
2023-01-11 22:17:14 +00:00
var returnType * schema . ObjectType
if fun . ReturnType != nil {
if objectType , ok := fun . ReturnType . ( * schema . ObjectType ) ; ok {
returnType = objectType
} else {
// TODO: remove when we add support for generalized return type for python
return "" , fmt . Errorf ( "python sdk-gen doesn't support non-Object return types for function %s" , fun . Token )
}
}
if returnType != nil {
mod . collectImports ( returnType . Properties , imports , false )
2020-08-19 08:16:47 +00:00
}
2022-12-09 13:36:44 +00:00
mod . genFunctionHeader ( w , fun , imports )
2020-08-19 08:16:47 +00:00
2021-06-24 16:17:55 +00:00
var baseName , awaitableName string
2023-01-11 22:17:14 +00:00
if returnType != nil {
baseName , awaitableName = awaitableTypeNames ( returnType . Token )
2021-06-24 16:17:55 +00:00
}
2020-01-21 22:45:48 +00:00
name := PyName ( tokenToName ( fun . Token ) )
2020-08-19 08:16:47 +00:00
// Export only the symbols we want exported.
fmt . Fprintf ( w , "__all__ = [\n" )
2023-01-11 22:17:14 +00:00
if returnType != nil {
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , " '%s',\n" , baseName )
fmt . Fprintf ( w , " '%s',\n" , awaitableName )
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
}
fmt . Fprintf ( w , " '%s',\n" , name )
if fun . NeedsOutputVersion ( ) {
2021-08-30 20:52:58 +00:00
fmt . Fprintf ( w , " '%s_output',\n" , name )
}
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , "]\n\n" )
2020-01-21 22:45:48 +00:00
if fun . DeprecationMessage != "" {
2020-06-17 17:14:11 +00:00
escaped := strings . ReplaceAll ( fun . DeprecationMessage , ` " ` , ` \" ` )
2020-11-09 14:28:28 +00:00
fmt . Fprintf ( w , "warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n\n" , escaped )
2020-01-21 22:45:48 +00:00
}
// If there is a return type, emit it.
retTypeName := ""
var rets [ ] * schema . Property
2023-01-11 22:17:14 +00:00
if returnType != nil {
retTypeName , rets = mod . genAwaitableType ( w , returnType ) , returnType . Properties
2020-08-21 03:51:32 +00:00
fmt . Fprintf ( w , "\n\n" )
2022-12-09 13:36:44 +00:00
} else {
retTypeName = "Awaitable[None]"
2020-01-21 22:45:48 +00:00
}
var args [ ] * schema . Property
if fun . Inputs != nil {
args = fun . Inputs . Properties
}
2021-08-30 20:52:58 +00:00
mod . genFunDef ( w , name , retTypeName , args , false /* wrapInput */ )
mod . genFunDocstring ( w , fun )
mod . genFunDeprecationMessage ( w , fun )
2020-01-21 22:45:48 +00:00
// Copy the function arguments into a dictionary.
2020-07-07 18:32:48 +00:00
fmt . Fprintf ( w , " __args__ = dict()\n" )
2020-01-21 22:45:48 +00:00
for _ , arg := range args {
// TODO: args validation.
fmt . Fprintf ( w , " __args__['%s'] = %s\n" , arg . Name , PyName ( arg . Name ) )
}
// If the caller explicitly specified a version, use it, otherwise inject this package's version.
2022-06-14 23:52:24 +00:00
fmt . Fprintf ( w , " opts = pulumi.InvokeOptions.merge(_utilities.get_invoke_opts_defaults(), opts)\n" )
2020-01-21 22:45:48 +00:00
// Now simply invoke the runtime function with the arguments.
2024-07-29 14:51:07 +00:00
trailingArgs := ""
2023-01-11 22:17:14 +00:00
if returnType != nil {
2020-08-19 08:16:47 +00:00
// Pass along the private output_type we generated, so any nested outputs classes are instantiated by
// the call to invoke.
2024-07-29 14:51:07 +00:00
trailingArgs += ", typ=" + baseName
2020-08-19 08:16:47 +00:00
}
2024-07-29 14:51:07 +00:00
// If the invoke is on a parameterized package, make sure we pass the
// parameter.
pkg , err := fun . PackageReference . Definition ( )
if err != nil {
return "" , err
}
if pkg . Parameterization != nil {
trailingArgs += ", package_ref=_utilities.get_package()"
}
fmt . Fprintf ( w , " __ret__ = pulumi.runtime.invoke('%s', __args__, opts=opts%s).value\n" , fun . Token , trailingArgs )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , "\n" )
// And copy the results to an object, if there are indeed any expected returns.
2023-01-11 22:17:14 +00:00
if returnType != nil {
2022-05-23 21:43:38 +00:00
fmt . Fprintf ( w , " return %s(" , retTypeName )
2020-01-21 22:45:48 +00:00
for i , ret := range rets {
2022-05-23 21:43:38 +00:00
if i > 0 {
fmt . Fprintf ( w , "," )
}
2023-06-20 15:44:16 +00:00
// Use the `pulumi.get()` utility instead of calling `__ret__.field` directly.
// This avoids calling getter functions which will print deprecation messages on unused
// fields and should be hidden from the user during Result instantiation.
fmt . Fprintf ( w , "\n %[1]s=pulumi.get(__ret__, '%[1]s')" , PyName ( ret . Name ) )
2020-01-21 22:45:48 +00:00
}
2022-05-23 21:43:38 +00:00
fmt . Fprintf ( w , ")\n" )
2020-01-21 22:45:48 +00:00
}
2021-08-30 20:52:58 +00:00
mod . genFunctionOutputVersion ( w , fun )
2020-01-21 22:45:48 +00:00
return w . String ( ) , nil
}
2021-08-30 20:52:58 +00:00
func ( mod * modContext ) genFunDocstring ( w io . Writer , fun * schema . Function ) {
var args [ ] * schema . Property
if fun . Inputs != nil {
args = fun . Inputs . Properties
}
// If this func has documentation, write it at the top of the docstring, otherwise use a generic comment.
docs := & bytes . Buffer { }
if fun . Comment != "" {
fmt . Fprintln ( docs , codegen . FilterExamples ( fun . Comment , "python" ) )
} else {
fmt . Fprintln ( docs , "Use this data source to access information about an existing resource." )
}
if len ( args ) > 0 {
fmt . Fprintln ( docs , "" )
for _ , arg := range args {
mod . genPropDocstring ( docs , PyName ( arg . Name ) , arg , true /*acceptMapping*/ )
}
}
printComment ( w , docs . String ( ) , " " )
}
func ( mod * modContext ) genFunDeprecationMessage ( w io . Writer , fun * schema . Function ) {
if fun . DeprecationMessage == "" {
return
}
name := PyName ( tokenToName ( fun . Token ) )
fmt . Fprintf ( w , " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n" , name , fun . DeprecationMessage )
}
// Generates the function signature line `def fn(...):` without the body.
func ( mod * modContext ) genFunDef ( w io . Writer , name , retTypeName string , args [ ] * schema . Property , wrapInput bool ) {
def := fmt . Sprintf ( "def %s(" , name )
var indent string
if len ( args ) > 0 {
indent = strings . Repeat ( " " , len ( def ) )
}
2023-01-13 18:56:53 +00:00
fmt . Fprint ( w , def )
2021-08-30 20:52:58 +00:00
for i , arg := range args {
var ind string
if i != 0 {
ind = indent
}
pname := PyName ( arg . Name )
var argType schema . Type
if wrapInput {
argType = & schema . OptionalType {
ElementType : & schema . InputType {
ElementType : arg . Type ,
} ,
}
} else {
argType = codegen . OptionalType ( arg )
}
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( argType , true /*input*/ , true /*acceptMapping*/ , false /*forDict*/ )
2021-08-30 20:52:58 +00:00
fmt . Fprintf ( w , "%s%s: %s = None,\n" , ind , pname , ty )
}
fmt . Fprintf ( w , "%sopts: Optional[pulumi.InvokeOptions] = None" , indent )
if retTypeName != "" {
fmt . Fprintf ( w , ") -> %s:\n" , retTypeName )
} else {
fmt . Fprintf ( w , "):\n" )
}
}
2023-01-11 22:17:14 +00:00
func returnTypeObject ( fun * schema . Function ) * schema . ObjectType {
if fun . ReturnType != nil {
if objectType , ok := fun . ReturnType . ( * schema . ObjectType ) ; ok && objectType != nil {
return objectType
}
}
return nil
}
2021-08-30 20:52:58 +00:00
// Generates `def ${fn}_output(..) version lifted to work on
// `Input`-wrapped arguments and producing an `Output`-wrapped result.
func ( mod * modContext ) genFunctionOutputVersion ( w io . Writer , fun * schema . Function ) {
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
if ! fun . NeedsOutputVersion ( ) {
2021-08-30 20:52:58 +00:00
return
}
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
returnType := returnTypeObject ( fun )
2021-08-30 20:52:58 +00:00
var retTypeName string
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
if returnType != nil {
originalOutputTypeName , _ := awaitableTypeNames ( returnType . Token )
retTypeName = fmt . Sprintf ( "pulumi.Output[%s]" , originalOutputTypeName )
} else {
retTypeName = "pulumi.Output[void]"
}
2021-08-30 20:52:58 +00:00
originalName := PyName ( tokenToName ( fun . Token ) )
2023-12-12 12:19:42 +00:00
outputSuffixedName := originalName + "_output"
2021-08-30 20:52:58 +00:00
var args [ ] * schema . Property
if fun . Inputs != nil {
args = fun . Inputs . Properties
}
fmt . Fprintf ( w , "\n\n@_utilities.lift_output_func(%s)\n" , originalName )
mod . genFunDef ( w , outputSuffixedName , retTypeName , args , true /*wrapInput*/ )
mod . genFunDocstring ( w , fun )
mod . genFunDeprecationMessage ( w , fun )
fmt . Fprintf ( w , " ...\n" )
}
2020-11-25 05:43:32 +00:00
func ( mod * modContext ) genEnums ( w io . Writer , enums [ ] * schema . EnumType ) error {
// Header
mod . genHeader ( w , false /*needsSDK*/ , nil )
// Enum import
fmt . Fprintf ( w , "from enum import Enum\n\n" )
// Export only the symbols we want exported.
fmt . Fprintf ( w , "__all__ = [\n" )
for _ , enum := range enums {
fmt . Fprintf ( w , " '%s',\n" , tokenToName ( enum . Token ) )
}
fmt . Fprintf ( w , "]\n\n\n" )
for i , enum := range enums {
if err := mod . genEnum ( w , enum ) ; err != nil {
return err
}
if i != len ( enums ) - 1 {
fmt . Fprintf ( w , "\n\n" )
}
}
return nil
}
func ( mod * modContext ) genEnum ( w io . Writer , enum * schema . EnumType ) error {
indent := " "
enumName := tokenToName ( enum . Token )
2024-06-18 15:24:42 +00:00
underlyingType := mod . typeString ( enum . ElementType , false , false , false /*forDict*/ )
2020-11-25 05:43:32 +00:00
switch enum . ElementType {
case schema . StringType , schema . IntType , schema . NumberType :
fmt . Fprintf ( w , "class %s(%s, Enum):\n" , enumName , underlyingType )
printComment ( w , enum . Comment , indent )
for _ , e := range enum . Elements {
// If the enum doesn't have a name, set the value as the name.
if e . Name == "" {
e . Name = fmt . Sprintf ( "%v" , e . Value )
}
2020-12-16 17:22:44 +00:00
name , err := makeSafeEnumName ( e . Name , enumName )
2020-11-25 05:43:32 +00:00
if err != nil {
return err
}
e . Name = name
fmt . Fprintf ( w , "%s%s = " , indent , e . Name )
if val , ok := e . Value . ( string ) ; ok {
fmt . Fprintf ( w , "%q\n" , val )
} else {
fmt . Fprintf ( w , "%v\n" , e . Value )
}
2021-06-28 20:06:50 +00:00
if e . Comment != "" {
2021-09-08 18:52:54 +00:00
printComment ( w , e . Comment , indent )
2021-06-28 20:06:50 +00:00
}
2020-11-25 05:43:32 +00:00
}
default :
2021-11-13 02:37:17 +00:00
return fmt . Errorf ( "enums of type %s are not yet implemented for this language" , enum . ElementType . String ( ) )
2020-11-25 05:43:32 +00:00
}
return nil
}
2021-06-24 16:17:55 +00:00
func visitObjectTypes ( properties [ ] * schema . Property , visitor func ( objectOrResource schema . Type ) ) {
codegen . VisitTypeClosure ( properties , func ( t schema . Type ) {
switch st := t . ( type ) {
2021-04-19 23:40:39 +00:00
case * schema . EnumType , * schema . ObjectType , * schema . ResourceType :
2021-06-24 16:17:55 +00:00
visitor ( st )
2021-04-19 23:40:39 +00:00
}
} )
2020-08-19 08:16:47 +00:00
}
2021-04-19 23:40:39 +00:00
func ( mod * modContext ) collectImports ( properties [ ] * schema . Property , imports imports , input bool ) {
2021-07-07 13:57:18 +00:00
mod . collectImportsForResource ( properties , imports , input , nil )
}
func ( mod * modContext ) collectImportsForResource ( properties [ ] * schema . Property , imports imports , input bool ,
2023-03-03 16:36:39 +00:00
res * schema . Resource ,
) {
2021-06-24 16:17:55 +00:00
codegen . VisitTypeClosure ( properties , func ( t schema . Type ) {
switch t := t . ( type ) {
2021-04-19 23:40:39 +00:00
case * schema . ObjectType :
imports . addType ( mod , t , input )
case * schema . EnumType :
2022-05-12 08:59:56 +00:00
imports . addEnum ( mod , t )
2021-04-19 23:40:39 +00:00
case * schema . ResourceType :
2021-07-07 13:57:18 +00:00
// Don't import itself.
if t . Resource != res {
imports . addResource ( mod , t )
}
2020-08-19 08:16:47 +00:00
}
2021-04-19 23:40:39 +00:00
} )
2020-08-19 08:16:47 +00:00
}
2023-03-03 16:36:39 +00:00
var (
requirementRegex = regexp . MustCompile ( ` ^>=([^,]+),<[^,]+$ ` )
pep440AlphaRegex = regexp . MustCompile ( ` ^(\d+\.\d+\.\d)+a(\d+)$ ` )
pep440BetaRegex = regexp . MustCompile ( ` ^(\d+\.\d+\.\d+)b(\d+)$ ` )
pep440RCRegex = regexp . MustCompile ( ` ^(\d+\.\d+\.\d+)rc(\d+)$ ` )
pep440DevRegex = regexp . MustCompile ( ` ^(\d+\.\d+\.\d+)\.dev(\d+)$ ` )
)
2020-01-21 22:45:48 +00:00
var oldestAllowedPulumi = semver . Version {
Major : 0 ,
Minor : 17 ,
Patch : 28 ,
}
2020-03-13 04:43:54 +00:00
func sanitizePackageDescription ( description string ) string {
lines := strings . SplitN ( description , "\n" , 2 )
if len ( lines ) > 0 {
return lines [ 0 ]
}
return ""
}
2020-12-04 03:22:16 +00:00
func genPulumiPluginFile ( pkg * schema . Package ) ( [ ] byte , error ) {
plugin := & plugin . PulumiPluginJSON {
Resource : true ,
Name : pkg . Name ,
Server : pkg . PluginDownloadURL ,
}
2022-02-03 16:07:13 +00:00
if info , ok := pkg . Language [ "python" ] . ( PackageInfo ) ; pkg . Version != nil && ok && info . RespectSchemaVersion {
plugin . Version = pkg . Version . String ( )
}
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
if pkg . SupportPack {
plugin . Version = pkg . Version . String ( )
}
2024-07-16 10:55:38 +00:00
if pkg . Parameterization != nil {
plugin . Name = pkg . Parameterization . BaseProvider . Name
plugin . Version = pkg . Parameterization . BaseProvider . Version . String ( )
plugin . Server = pkg . Parameterization . BaseProvider . PluginDownloadURL
}
2022-02-03 16:07:13 +00:00
2020-12-04 03:22:16 +00:00
return plugin . JSON ( )
}
2020-01-21 22:45:48 +00:00
// genPackageMetadata generates all the non-code metadata required by a Pulumi package.
2020-12-04 03:22:16 +00:00
func genPackageMetadata (
2023-03-03 16:36:39 +00:00
tool string , pkg * schema . Package , pyPkgName string , requires map [ string ] string , pythonRequires string ,
) ( string , error ) {
2020-01-21 22:45:48 +00:00
w := & bytes . Buffer { }
2020-09-11 21:27:36 +00:00
( & modContext { tool : tool } ) . genHeader ( w , false /*needsSDK*/ , nil )
2020-01-21 22:45:48 +00:00
// Now create a standard Python package from the metadata.
fmt . Fprintf ( w , "import errno\n" )
fmt . Fprintf ( w , "from setuptools import setup, find_packages\n" )
fmt . Fprintf ( w , "from setuptools.command.install import install\n" )
fmt . Fprintf ( w , "from subprocess import check_call\n" )
2020-06-10 22:11:59 +00:00
fmt . Fprintf ( w , "\n\n" )
2020-01-21 22:45:48 +00:00
2021-09-23 02:39:07 +00:00
// Create a constant for the version number to replace during build
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
version := "\"0.0.0\""
2022-02-03 16:07:13 +00:00
info , ok := pkg . Language [ "python" ] . ( PackageInfo )
if pkg . Version != nil && ok && info . RespectSchemaVersion {
2024-01-30 15:02:59 +00:00
version = "\"" + PypiVersion ( * pkg . Version ) + "\""
2022-02-03 16:07:13 +00:00
}
2024-06-18 15:24:42 +00:00
if ok && info . InputTypes == InputTypesSettingClassesAndDicts {
// Add typing-extensions to the requires
updatedRequires := make ( map [ string ] string , len ( requires ) )
for key , value := range requires {
updatedRequires [ key ] = value
}
updatedRequires [ "typing-extensions" ] = ">=4.11,<5; python_version < \"3.11\""
requires = updatedRequires
}
2024-07-16 10:55:38 +00:00
// Parameterized schemas _always_ respect schema version
if pkg . SupportPack || pkg . Parameterization != nil {
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
if pkg . Version == nil {
return "" , errors . New ( "package version is required" )
}
version = "\"" + PypiVersion ( * pkg . Version ) + "\""
}
2024-01-30 15:02:59 +00:00
fmt . Fprintf ( w , "VERSION = %s\n" , version )
2020-01-21 22:45:48 +00:00
// Generate a readme method which will load README.rst, we use this to fill out the
// long_description field in the setup call.
fmt . Fprintf ( w , "def readme():\n" )
2021-05-21 03:40:30 +00:00
fmt . Fprintf ( w , " try:\n" )
fmt . Fprintf ( w , " with open('README.md', encoding='utf-8') as f:\n" )
fmt . Fprintf ( w , " return f.read()\n" )
fmt . Fprintf ( w , " except FileNotFoundError:\n" )
2021-06-24 21:01:49 +00:00
fmt . Fprintf ( w , " return \"%s Pulumi Package - Development Version\"\n" , pkg . Name )
2020-06-10 22:11:59 +00:00
fmt . Fprintf ( w , "\n\n" )
2020-01-21 22:45:48 +00:00
// Finally, the actual setup part.
2021-05-06 18:18:14 +00:00
fmt . Fprintf ( w , "setup(name='%s',\n" , pyPkgName )
2023-03-30 16:31:49 +00:00
// Supply a default Python version if the schema does not provide one.
pythonVersion := defaultMinPythonVersion
if minPythonVersion , err := minimumPythonVersion ( info ) ; err == nil {
pythonVersion = minPythonVersion
2021-10-26 21:33:21 +00:00
}
2023-03-30 16:31:49 +00:00
fmt . Fprintf ( w , " python_requires='%s',\n" , pythonVersion )
2021-07-14 23:44:22 +00:00
fmt . Fprintf ( w , " version=VERSION,\n" )
2020-01-21 22:45:48 +00:00
if pkg . Description != "" {
2020-03-13 04:43:54 +00:00
fmt . Fprintf ( w , " description=%q,\n" , sanitizePackageDescription ( pkg . Description ) )
2020-01-21 22:45:48 +00:00
}
fmt . Fprintf ( w , " long_description=readme(),\n" )
fmt . Fprintf ( w , " long_description_content_type='text/markdown',\n" )
if pkg . Keywords != nil {
fmt . Fprintf ( w , " keywords='" )
for i , kw := range pkg . Keywords {
if i > 0 {
fmt . Fprint ( w , " " )
}
fmt . Fprint ( w , kw )
}
fmt . Fprintf ( w , "',\n" )
}
2023-05-02 17:20:28 +00:00
urls := mapURLs ( pkg )
if homepage , ok := urls [ "Homepage" ] ; ok {
fmt . Fprintf ( w , " url='%s',\n" , homepage )
2020-01-21 22:45:48 +00:00
}
2023-05-02 17:20:28 +00:00
if repo , ok := urls [ "Repository" ] ; ok {
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " project_urls={\n" )
2023-05-02 17:20:28 +00:00
fmt . Fprintf ( w , " 'Repository': '%s'\n" , repo )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " },\n" )
}
if pkg . License != "" {
fmt . Fprintf ( w , " license='%s',\n" , pkg . License )
}
fmt . Fprintf ( w , " packages=find_packages(),\n" )
2020-02-07 17:43:20 +00:00
// Publish type metadata: PEP 561
fmt . Fprintf ( w , " package_data={\n" )
2021-05-06 18:18:14 +00:00
fmt . Fprintf ( w , " '%s': [\n" , pyPkgName )
2020-12-04 03:22:16 +00:00
fmt . Fprintf ( w , " 'py.typed',\n" )
2021-12-15 18:41:44 +00:00
fmt . Fprintf ( w , " 'pulumi-plugin.json',\n" )
2020-12-04 03:22:16 +00:00
2020-02-07 17:43:20 +00:00
fmt . Fprintf ( w , " ]\n" )
fmt . Fprintf ( w , " },\n" )
2023-05-02 16:46:59 +00:00
// Collect the deps into a tuple, where the first
// element is the dep name and the second element
// is the version constraint.
deps , err := calculateDeps ( requires )
if err != nil {
return "" , err
2020-01-21 22:45:48 +00:00
}
2023-05-02 16:46:59 +00:00
fmt . Fprintf ( w , " install_requires=[\n " )
// Concat the first and second element together,
// and break each element apart with a comman and a newline.
2023-06-28 16:02:04 +00:00
depStrings := slice . Prealloc [ string ] ( len ( deps ) )
2023-05-02 16:46:59 +00:00
for _ , dep := range deps {
concat := fmt . Sprintf ( "'%s%s'" , dep [ 0 ] , dep [ 1 ] )
depStrings = append ( depStrings , concat )
2020-01-21 22:45:48 +00:00
}
2023-05-02 16:46:59 +00:00
allDeps := strings . Join ( depStrings , ",\n " )
// Lastly, write the deps to the buffer.
fmt . Fprintf ( w , "%s\n ],\n" , allDeps )
2020-01-21 22:45:48 +00:00
fmt . Fprintf ( w , " zip_safe=False)\n" )
return w . String ( ) , nil
}
2023-03-28 20:42:38 +00:00
// errNoMinimumPythonVersion is a non-fatal error indicating that the schema
// did not provide a minimum version of Python for the Package.
var errNoMinimumPythonVersion = errors . New ( "the schema does not provide a minimum version of Python required for this provider. It's recommended to provide a minimum version so package users can understand the package's requirements" )
// minimumPythonVersion returns a string containing the version
// constraint specifying the minimumal version of Python required
// by this package. For example, ">=3.8" is satified by all versions
// of Python greater than or equal to Python 3.8.
func minimumPythonVersion ( info PackageInfo ) ( string , error ) {
if info . PythonRequires != "" {
return info . PythonRequires , nil
}
return "" , errNoMinimumPythonVersion
}
2020-01-21 22:45:48 +00:00
func pep440VersionToSemver ( v string ) ( semver . Version , error ) {
switch {
case pep440AlphaRegex . MatchString ( v ) :
parts := pep440AlphaRegex . FindStringSubmatch ( v )
v = parts [ 1 ] + "-alpha." + parts [ 2 ]
case pep440BetaRegex . MatchString ( v ) :
parts := pep440BetaRegex . FindStringSubmatch ( v )
v = parts [ 1 ] + "-beta." + parts [ 2 ]
case pep440RCRegex . MatchString ( v ) :
parts := pep440RCRegex . FindStringSubmatch ( v )
v = parts [ 1 ] + "-rc." + parts [ 2 ]
case pep440DevRegex . MatchString ( v ) :
parts := pep440DevRegex . FindStringSubmatch ( v )
v = parts [ 1 ] + "-dev." + parts [ 2 ]
}
return semver . ParseTolerant ( v )
}
// genInitDocstring emits the docstring for the __init__ method of the given resource type.
//
// Sphinx (the documentation generator that we use to generate Python docs) does not draw a
// distinction between documentation comments on the class itself and documentation comments on the
// __init__ method of a class. The docs repo instructs Sphinx to concatenate the two together, which
// means that we don't need to emit docstrings on the class at all as long as the __init__ docstring
// is good enough.
//
// The docstring we generate here describes both the class itself and the arguments to the class's
// constructor. The format of the docstring is in "Sphinx form":
2022-09-14 02:12:02 +00:00
// 1. Parameters are introduced using the syntax ":param <type> <name>: <comment>". Sphinx parses this and uses it
// to populate the list of parameters for this function.
// 2. The doc string of parameters is expected to be indented to the same indentation as the type of the parameter.
// Sphinx will complain and make mistakes if this is not the case.
// 3. The doc string can't have random newlines in it, or Sphinx will complain.
2020-01-21 22:45:48 +00:00
//
// This function does the best it can to navigate these constraints and produce a docstring that
// Sphinx can make sense of.
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
func ( mod * modContext ) genInitDocstring ( w io . Writer , res * schema . Resource , resourceArgsName string , argOverload bool ) {
2020-01-21 22:45:48 +00:00
// b contains the full text of the docstring, without the leading and trailing triple quotes.
b := & bytes . Buffer { }
// If this resource has documentation, write it at the top of the docstring, otherwise use a generic comment.
if res . Comment != "" {
2020-06-18 19:32:15 +00:00
fmt . Fprintln ( b , codegen . FilterExamples ( res . Comment , "python" ) )
2020-01-21 22:45:48 +00:00
} else {
fmt . Fprintf ( b , "Create a %s resource with the given unique name, props, and options.\n" , tokenToName ( res . Token ) )
}
// All resources have a resource_name parameter and opts parameter.
fmt . Fprintln ( b , ":param str resource_name: The name of the resource." )
2021-04-02 17:09:17 +00:00
if argOverload {
[codegen/python] Rename conflicting ResourceArgs classes (#7171)
Python resource constructor overloads were recently added that accept a
`<Resource>Args` class for input properties, as an alternative to the
other constructor overload that accepts keyword arguments. The name of
the new args class is the name of the resource concatenated with an
`Args` suffix.
Some providers (e.g. Kubernetes, Azure Native, and Google Native) have
input types with the same name as resources in the same module, which
results in two different `<Resource>Args` classes in the same module.
When you try to use the new args class with the constructor, e.g.:
```python
pulumi_kubernetes.storage.v1.StorageClass(
resource_name='string',
args=pulumi_kubernetes.storage.v1.StorageClassArgs(...),
opts=pulumi.ResourceOptions(...),
)
```
You run into an error, because
`pulumi_kubernetes.storage.v1.StorageClassArgs` is actually referring to
the existing input type rather than the intended `StorageClassArgs`
class for the constructor arguments.
Having the duplicate classes hasn't broken existing usage of the input
type because we "export" all the input types for a module _after_ all
the resources and resource args classes are exported, so the input type
just ends up "overwriting" the duplicate resource args class.
Other languages don't have this problem because the input type is either
in it's own module/namespace (e.g. Node.js and .NET) or a different name
is used for the input type (Go). But with Python, the input types and
resources are all available in the same module.
To address this for Python, when there is an input type in the same
module with the same name as the resource, the args class for the
resource will be emitted as `<Resource>InitArgs` instead of
`<Resource>Args`.
2021-06-10 17:41:49 +00:00
fmt . Fprintf ( b , ":param %s args: The arguments to use to populate this resource's properties.\n" ,
resourceArgsName )
2021-04-02 17:09:17 +00:00
}
2020-01-21 22:45:48 +00:00
fmt . Fprintln ( b , ":param pulumi.ResourceOptions opts: Options for the resource." )
2021-04-02 17:09:17 +00:00
if ! argOverload {
for _ , prop := range res . InputProperties {
2021-06-24 16:17:55 +00:00
mod . genPropDocstring ( b , InitParamName ( prop . Name ) , prop , true /*acceptMapping*/ )
2021-04-02 17:09:17 +00:00
}
2020-06-15 23:17:05 +00:00
}
2020-01-21 22:45:48 +00:00
// printComment handles the prefix and triple quotes.
printComment ( w , b . String ( ) , " " )
}
func ( mod * modContext ) genGetDocstring ( w io . Writer , res * schema . Resource ) {
// "buf" contains the full text of the docstring, without the leading and trailing triple quotes.
b := & bytes . Buffer { }
fmt . Fprintf ( b , "Get an existing %s resource's state with the given name, id, and optional extra\n" +
"properties used to qualify the lookup.\n" , tokenToName ( res . Token ) )
fmt . Fprintln ( b , "" )
fmt . Fprintln ( b , ":param str resource_name: The unique name of the resulting resource." )
2020-08-19 08:16:47 +00:00
fmt . Fprintln ( b , ":param pulumi.Input[str] id: The unique provider ID of the resource to lookup." )
2020-01-21 22:45:48 +00:00
fmt . Fprintln ( b , ":param pulumi.ResourceOptions opts: Options for the resource." )
2020-06-08 19:44:57 +00:00
if res . StateInputs != nil {
for _ , prop := range res . StateInputs . Properties {
2021-06-24 16:17:55 +00:00
mod . genPropDocstring ( b , InitParamName ( prop . Name ) , prop , true /*acceptMapping*/ )
2020-06-08 19:44:57 +00:00
}
2020-08-19 08:16:47 +00:00
}
// printComment handles the prefix and triple quotes.
printComment ( w , b . String ( ) , " " )
}
2021-06-24 16:17:55 +00:00
func ( mod * modContext ) genTypeDocstring ( w io . Writer , comment string , properties [ ] * schema . Property ) {
2020-08-19 08:16:47 +00:00
// b contains the full text of the docstring, without the leading and trailing triple quotes.
b := & bytes . Buffer { }
// If this type has documentation, write it at the top of the docstring.
if comment != "" {
fmt . Fprintln ( b , comment )
}
2020-01-21 22:45:48 +00:00
2020-08-19 08:16:47 +00:00
for _ , prop := range properties {
2021-06-24 16:17:55 +00:00
mod . genPropDocstring ( b , PyName ( prop . Name ) , prop , false /*acceptMapping*/ )
2020-06-08 19:44:57 +00:00
}
2020-01-21 22:45:48 +00:00
// printComment handles the prefix and triple quotes.
printComment ( w , b . String ( ) , " " )
}
2021-06-24 16:17:55 +00:00
func ( mod * modContext ) genPropDocstring ( w io . Writer , name string , prop * schema . Property , acceptMapping bool ) {
2020-01-21 22:45:48 +00:00
if prop . Comment == "" {
return
}
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( codegen . RequiredType ( prop ) , true , acceptMapping , false /*forDict*/ )
2020-01-21 22:45:48 +00:00
// If this property has some documentation associated with it, we need to split it so that it is indented
// in a way that Sphinx can understand.
lines := strings . Split ( prop . Comment , "\n" )
for len ( lines ) > 0 && lines [ len ( lines ) - 1 ] == "" {
lines = lines [ : len ( lines ) - 1 ]
}
for i , docLine := range lines {
// If it's the first line, print the :param header.
if i == 0 {
fmt . Fprintf ( w , ":param %s %s: %s\n" , ty , name , docLine )
} else {
// Otherwise, print out enough padding to align with the first char of the type.
fmt . Fprintf ( w , " %s\n" , docLine )
}
}
}
2024-06-18 15:24:42 +00:00
func ( mod * modContext ) typeString ( t schema . Type , input , acceptMapping bool , forDict bool ) string {
2020-08-19 08:16:47 +00:00
switch t := t . ( type ) {
2021-06-24 16:17:55 +00:00
case * schema . OptionalType :
2024-06-18 15:24:42 +00:00
typ := mod . typeString ( t . ElementType , input , acceptMapping , forDict )
if forDict {
return fmt . Sprintf ( "NotRequired[%s]" , typ )
}
return fmt . Sprintf ( "Optional[%s]" , typ )
2021-06-24 16:17:55 +00:00
case * schema . InputType :
2024-06-18 15:24:42 +00:00
typ := mod . typeString ( codegen . SimplifyInputUnion ( t . ElementType ) , input , acceptMapping , forDict )
2021-06-24 16:17:55 +00:00
if typ == "Any" {
return typ
}
return fmt . Sprintf ( "pulumi.Input[%s]" , typ )
2020-11-25 05:43:32 +00:00
case * schema . EnumType :
2023-07-25 18:57:07 +00:00
return mod . enumType ( t )
2020-08-19 08:16:47 +00:00
case * schema . ArrayType :
2024-06-18 15:24:42 +00:00
return fmt . Sprintf ( "Sequence[%s]" , mod . typeString ( t . ElementType , input , acceptMapping , forDict ) )
2020-08-19 08:16:47 +00:00
case * schema . MapType :
2024-06-18 15:24:42 +00:00
return fmt . Sprintf ( "Mapping[str, %s]" , mod . typeString ( t . ElementType , input , acceptMapping , forDict ) )
2020-08-19 08:16:47 +00:00
case * schema . ObjectType :
2024-06-18 15:24:42 +00:00
if forDict {
return mod . objectType ( t , input , true /*dictType*/ )
}
typ := mod . objectType ( t , input , false /*dictType*/ )
2021-06-24 16:17:55 +00:00
if ! acceptMapping {
return typ
2020-01-21 22:45:48 +00:00
}
2024-06-18 15:24:42 +00:00
// If the type is an input and the TypedDict generation is enabled for the type's package, we
// we can emit `Union[type, dictType]` and avoid the `InputType[]` wrapper.
// dictType covers the Mapping case in `InputType = Union[T, Mapping[str, Any]]`.
pkg , err := t . PackageReference . Definition ( )
contract . AssertNoErrorf ( err , "error loading definition for package %q" , t . PackageReference . Name ( ) )
info , ok := pkg . Language [ "python" ] . ( PackageInfo )
2024-07-23 11:26:54 +00:00
// TODO[https://github.com/pulumi/pulumi/issues/16702]
// We don't yet assume that external packages support TypedDicts by default.
// Remove samePackage condition to enable TypedDicts for external packages by default.
samePackage := codegen . PkgEquals ( t . PackageReference , mod . pkg )
typedDicts := ok && typedDictEnabled ( info . InputTypes ) && samePackage
2024-06-18 15:24:42 +00:00
if typedDicts && input {
return fmt . Sprintf ( "Union[%s, %s]" , typ , mod . objectType ( t , input , true /*dictType*/ ) )
}
2021-06-24 16:17:55 +00:00
return fmt . Sprintf ( "pulumi.InputType[%s]" , typ )
2020-09-23 19:39:25 +00:00
case * schema . ResourceType :
2021-06-24 16:17:55 +00:00
return fmt . Sprintf ( "'%s'" , mod . resourceType ( t ) )
2020-08-19 08:16:47 +00:00
case * schema . TokenType :
// Use the underlying type for now.
if t . UnderlyingType != nil {
2024-06-18 15:24:42 +00:00
return mod . typeString ( t . UnderlyingType , input , acceptMapping , forDict )
2020-08-19 08:16:47 +00:00
}
2021-06-24 16:17:55 +00:00
return "Any"
2020-08-19 08:16:47 +00:00
case * schema . UnionType :
if ! input {
2020-11-25 05:43:32 +00:00
for _ , e := range t . ElementTypes {
// If this is an output and a "relaxed" enum, emit the type as the underlying primitive type rather than the union.
// Eg. Output[str] rather than Output[Any]
if typ , ok := e . ( * schema . EnumType ) ; ok {
2024-06-18 15:24:42 +00:00
return mod . typeString ( typ . ElementType , input , acceptMapping , forDict )
2020-11-25 05:43:32 +00:00
}
}
2020-08-19 08:16:47 +00:00
if t . DefaultType != nil {
2024-06-18 15:24:42 +00:00
return mod . typeString ( t . DefaultType , input , acceptMapping , forDict )
2020-08-19 08:16:47 +00:00
}
2021-06-24 16:17:55 +00:00
return "Any"
}
2020-01-21 22:45:48 +00:00
2021-06-24 16:17:55 +00:00
elementTypeSet := codegen . NewStringSet ( )
2023-06-28 16:02:04 +00:00
elements := slice . Prealloc [ string ] ( len ( t . ElementTypes ) )
2021-06-24 16:17:55 +00:00
for _ , e := range t . ElementTypes {
2024-06-18 15:24:42 +00:00
et := mod . typeString ( e , input , acceptMapping , forDict )
2021-06-24 16:17:55 +00:00
if ! elementTypeSet . Has ( et ) {
elementTypeSet . Add ( et )
elements = append ( elements , et )
2020-01-21 22:45:48 +00:00
}
2021-06-24 16:17:55 +00:00
}
2020-01-21 22:45:48 +00:00
2021-06-24 16:17:55 +00:00
if len ( elements ) == 1 {
return elements [ 0 ]
2020-08-19 08:16:47 +00:00
}
2021-06-24 16:17:55 +00:00
return fmt . Sprintf ( "Union[%s]" , strings . Join ( elements , ", " ) )
2020-08-19 08:16:47 +00:00
default :
switch t {
case schema . BoolType :
2021-06-24 16:17:55 +00:00
return "bool"
2020-09-11 21:56:29 +00:00
case schema . IntType :
2021-06-24 16:17:55 +00:00
return "int"
2020-09-11 21:56:29 +00:00
case schema . NumberType :
2021-06-24 16:17:55 +00:00
return "float"
2020-08-19 08:16:47 +00:00
case schema . StringType :
2021-06-24 16:17:55 +00:00
return "str"
2020-08-19 08:16:47 +00:00
case schema . ArchiveType :
2021-06-24 16:17:55 +00:00
return "pulumi.Archive"
2020-08-19 08:16:47 +00:00
case schema . AssetType :
2021-06-24 16:17:55 +00:00
return "Union[pulumi.Asset, pulumi.Archive]"
2020-08-19 08:16:47 +00:00
case schema . JSONType :
fallthrough
case schema . AnyType :
2021-06-24 16:17:55 +00:00
return "Any"
2020-01-21 22:45:48 +00:00
}
}
2021-06-24 16:17:55 +00:00
panic ( fmt . Errorf ( "unexpected type %T" , t ) )
2020-01-21 22:45:48 +00:00
}
// pyType returns the expected runtime type for the given variable. Of course, being a dynamic language, this
// check is not exhaustive, but it should be good enough to catch 80% of the cases early on.
2020-09-23 19:39:25 +00:00
func ( mod * modContext ) pyType ( typ schema . Type ) string {
2020-02-05 19:43:39 +00:00
switch typ := typ . ( type ) {
2021-06-24 16:17:55 +00:00
case * schema . OptionalType :
return mod . pyType ( typ . ElementType )
2020-11-25 05:43:32 +00:00
case * schema . EnumType :
return mod . pyType ( typ . ElementType )
2020-01-21 22:45:48 +00:00
case * schema . ArrayType :
return "list"
2020-02-05 19:43:39 +00:00
case * schema . MapType , * schema . ObjectType , * schema . UnionType :
return "dict"
2020-09-23 19:39:25 +00:00
case * schema . ResourceType :
2021-02-12 23:54:19 +00:00
return mod . resourceType ( typ )
2020-02-05 19:43:39 +00:00
case * schema . TokenType :
if typ . UnderlyingType != nil {
2020-09-23 19:39:25 +00:00
return mod . pyType ( typ . UnderlyingType )
2020-02-05 19:43:39 +00:00
}
2020-01-21 22:45:48 +00:00
return "dict"
default :
switch typ {
case schema . BoolType :
return "bool"
2020-09-11 21:56:29 +00:00
case schema . IntType :
return "int"
case schema . NumberType :
2020-01-21 22:45:48 +00:00
return "float"
case schema . StringType :
return "str"
case schema . ArchiveType :
return "pulumi.Archive"
case schema . AssetType :
return "Union[pulumi.Asset, pulumi.Archive]"
default :
return "dict"
}
}
}
2020-02-07 17:43:20 +00:00
func isStringType ( t schema . Type ) bool {
2021-06-24 16:17:55 +00:00
t = codegen . UnwrapType ( t )
2020-02-07 17:43:20 +00:00
for tt , ok := t . ( * schema . TokenType ) ; ok ; tt , ok = t . ( * schema . TokenType ) {
t = tt . UnderlyingType
}
return t == schema . StringType
}
2020-01-21 22:45:48 +00:00
// pyPack returns the suggested package name for the given string.
func pyPack ( s string ) string {
2020-09-15 11:56:58 +00:00
return "pulumi_" + strings . ReplaceAll ( s , "-" , "_" )
2020-01-21 22:45:48 +00:00
}
// pyClassName turns a raw name into one that is suitable as a Python class name.
func pyClassName ( name string ) string {
return EnsureKeywordSafe ( name )
}
2020-08-19 15:34:06 +00:00
// InitParamName returns a PyName-encoded name but also deduplicates the name against built-in parameters of resource __init__.
func InitParamName ( name string ) string {
2020-07-30 14:29:36 +00:00
result := PyName ( name )
switch result {
case "resource_name" , "opts" :
return result + "_"
default :
return result
}
}
2021-04-16 02:03:28 +00:00
func ( mod * modContext ) genObjectType ( w io . Writer , obj * schema . ObjectType , input bool ) error {
2021-06-24 16:17:55 +00:00
name := mod . unqualifiedObjectTypeName ( obj , input )
resourceOutputType := ! input && mod . details ( obj ) . resourceOutputType
2024-06-18 15:24:42 +00:00
if input && typedDictEnabled ( mod . inputTypes ) {
if err := mod . genDictType ( w , name , obj . Comment , obj . Properties ) ; err != nil {
return err
}
}
2021-06-24 16:17:55 +00:00
return mod . genType ( w , name , obj . Comment , obj . Properties , input , resourceOutputType )
2021-04-16 02:03:28 +00:00
}
2021-06-24 16:17:55 +00:00
func ( mod * modContext ) genType ( w io . Writer , name , comment string , properties [ ] * schema . Property , input , resourceOutput bool ) error {
2020-08-19 08:16:47 +00:00
// Sort required props first.
2021-04-02 17:09:17 +00:00
props := make ( [ ] * schema . Property , len ( properties ) )
copy ( props , properties )
2020-08-19 08:16:47 +00:00
sort . Slice ( props , func ( i , j int ) bool {
pi , pj := props [ i ] , props [ j ]
switch {
2021-06-24 16:17:55 +00:00
case pi . IsRequired ( ) != pj . IsRequired ( ) :
return pi . IsRequired ( ) && ! pj . IsRequired ( )
2020-08-19 08:16:47 +00:00
default :
return pi . Name < pj . Name
}
} )
decorator := "@pulumi.output_type"
if input {
decorator = "@pulumi.input_type"
}
var suffix string
if ! input {
suffix = "(dict)"
}
2022-10-17 16:37:07 +00:00
name = pythonCase ( name )
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , "%s\n" , decorator )
fmt . Fprintf ( w , "class %s%s:\n" , name , suffix )
2021-04-02 17:09:17 +00:00
if ! input && comment != "" {
printComment ( w , comment , " " )
2020-08-19 08:16:47 +00:00
}
2021-04-07 19:35:19 +00:00
// To help users migrate to using the properly snake_cased property getters, emit warnings when camelCase keys are
// accessed. We emit this at the top of the class in case we have a `get` property that will be redefined later.
2021-04-19 23:40:39 +00:00
if resourceOutput {
2021-04-07 19:35:19 +00:00
var needsCaseWarning bool
for _ , prop := range props {
pname := PyName ( prop . Name )
if pname != prop . Name {
needsCaseWarning = true
break
}
}
if needsCaseWarning {
fmt . Fprintf ( w , " @staticmethod\n" )
fmt . Fprintf ( w , " def __key_warning(key: str):\n" )
fmt . Fprintf ( w , " suggest = None\n" )
prefix := "if"
for _ , prop := range props {
pname := PyName ( prop . Name )
if pname == prop . Name {
continue
}
fmt . Fprintf ( w , " %s key == %q:\n" , prefix , prop . Name )
fmt . Fprintf ( w , " suggest = %q\n" , pname )
prefix = "elif"
}
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , " if suggest:\n" )
fmt . Fprintf ( w , " pulumi.log.warn(f\"Key '{key}' not found in %s. Access the value via the '{suggest}' property getter instead.\")\n" , name )
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , " def __getitem__(self, key: str) -> Any:\n" )
fmt . Fprintf ( w , " %s.__key_warning(key)\n" , name )
fmt . Fprintf ( w , " return super().__getitem__(key)\n" )
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , " def get(self, key: str, default = None) -> Any:\n" )
fmt . Fprintf ( w , " %s.__key_warning(key)\n" , name )
fmt . Fprintf ( w , " return super().get(key, default)\n" )
fmt . Fprintf ( w , "\n" )
}
}
2020-08-19 08:16:47 +00:00
// Generate an __init__ method.
fmt . Fprintf ( w , " def __init__(__self__" )
// Bare `*` argument to force callers to use named arguments.
if len ( props ) > 0 {
fmt . Fprintf ( w , ", *" )
}
for _ , prop := range props {
pname := PyName ( prop . Name )
2024-06-18 15:24:42 +00:00
ty := mod . typeString ( prop . Type , input , false /*acceptMapping*/ , false /*forDict*/ )
2023-09-12 16:03:58 +00:00
if prop . DefaultValue != nil {
2024-06-18 15:24:42 +00:00
ty = mod . typeString ( codegen . OptionalType ( prop ) , input , false /*acceptMapping*/ , false /*forDict*/ )
2023-09-12 16:03:58 +00:00
}
2020-08-19 08:16:47 +00:00
var defaultValue string
2023-09-12 16:03:58 +00:00
if ! prop . IsRequired ( ) || prop . DefaultValue != nil {
2020-08-19 08:16:47 +00:00
defaultValue = " = None"
}
fmt . Fprintf ( w , ",\n %s: %s%s" , pname , ty , defaultValue )
}
fmt . Fprintf ( w , "):\n" )
2021-06-24 16:17:55 +00:00
mod . genTypeDocstring ( w , comment , props )
2023-09-13 16:57:27 +00:00
if len ( props ) == 0 {
fmt . Fprintf ( w , " pass\n" )
}
2020-08-19 08:16:47 +00:00
for _ , prop := range props {
pname := PyName ( prop . Name )
var arg interface { }
var err error
2023-09-12 16:03:58 +00:00
// Check that the property isn't deprecated.
if input && prop . DeprecationMessage != "" {
escaped := strings . ReplaceAll ( prop . DeprecationMessage , ` " ` , ` \" ` )
fmt . Fprintf ( w , " if %s is not None:\n" , pname )
fmt . Fprintf ( w , " warnings.warn(\"\"\"%s\"\"\", DeprecationWarning)\n" , escaped )
fmt . Fprintf ( w , " pulumi.log.warn(\"\"\"%s is deprecated: %s\"\"\")\n" , pname , escaped )
}
2020-08-19 08:16:47 +00:00
// Fill in computed defaults for arguments.
if prop . DefaultValue != nil {
2021-06-24 16:17:55 +00:00
dv , err := getDefaultValue ( prop . DefaultValue , codegen . UnwrapType ( prop . Type ) )
2020-08-19 08:16:47 +00:00
if err != nil {
return err
}
fmt . Fprintf ( w , " if %s is None:\n" , pname )
fmt . Fprintf ( w , " %s = %s\n" , pname , dv )
}
// And add it to the dictionary.
arg = pname
if prop . ConstValue != nil {
arg , err = getConstValue ( prop . ConstValue )
if err != nil {
return err
}
}
var indent string
2021-06-24 16:17:55 +00:00
if ! prop . IsRequired ( ) {
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , " if %s is not None:\n" , pname )
indent = " "
}
[sdkgen/python] revert changes introducing `_configure` (#14427)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/14418
Reopens https://github.com/pulumi/pulumi/issues/12546
This removes the `_configure()` ResourceArgs helper method as it has
caused a number of issues (linked below).
`_configure()` was added in order to support initializing default values
sdk side for python. This has led to the following PRs being merged to
address unexpected bugs.
## Overview of fixes:
It will be quite hard to demonstrate without examples, so I will give an
example of the code and describe its short comings:
---
- https://github.com/pulumi/pulumi/issues/14418
```python
args = MyArgs() # fails as required argument foo not provided
args.foo = "Hello, World!"
```
---
- https://github.com/pulumi/pulumi/pull/14235
Supporting `imageName` and `image_name`
```python
def _configure(...
image_name, # required
...):
...
# This should not error, but errors.
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14281
```python
def _configure(...
image_name, # required
...
**kwargs):
...
# This should not fail, but fails as `image_name` is not provided
_configure(imageName="debian")
```
---
- https://github.com/pulumi/pulumi/pull/14014
```python
class Thing:
def __init__(self,
...
# Optional
certmanager=None,
...):
...
Thing._configure(
...
certmanager=None,
...)
...
def _configure(...):
...
# This block runs when certmanager = None, but should not.
if not isinstance(certmanager, ProviderCertmanagerArgs):
certmanager = certmanager or {}
def _setter(key, value):
certmanager[key] = value
ProviderCertmanagerArgs._configure(_setter, **certmanager)
...
Provider()
```
---
- https://github.com/pulumi/pulumi/pull/14321
```python
registry_info=accessToken.apply(get_registry_info)
# Build and publish the image.
image = Image(
'my-image',
build=DockerBuildArgs(
context='app',
),
image_name=image_name,
# Note that this is an Output.
registry=registry_info,
)
# registry is not None and it is not an instance of RegistryArgs, so we fall into the if, thinking it is a dict, but it is an Output.
if registry is not None and not isinstance(registry, RegistryArgs):
registry = registry or {}
def _setter(key, value):
registry[key] = value
RegistryArgs._configure(_setter, **registry)
__props__.__dict__["registry"] = registry
```
---
- https://github.com/pulumi/pulumi/pull/14318
```python
# foo.core.v1.PodArgs may be an external type and may not be upgraded to have _configure() and will fail, but should not.
if pod is not None and not isinstance(pod, foo.core.v1.PodArgs):
pod = pod or {}
def _setter(key, value):
pod[key] = value
pulumi_kubernetes.core.v1.PodArgs._configure(_setter, **pod)
```
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-27 22:28:41 +00:00
fmt . Fprintf ( w , "%s pulumi.set(__self__, \"%s\", %s)\n" , indent , pname , arg )
2020-08-19 08:16:47 +00:00
}
fmt . Fprintf ( w , "\n" )
// Generate properties. Input types have getters and setters, output types only have getters.
2021-07-07 13:57:18 +00:00
mod . genProperties ( w , props , input /*setters*/ , "" , func ( prop * schema . Property ) string {
2024-06-18 15:24:42 +00:00
return mod . typeString ( prop . Type , input , false /*acceptMapping*/ , false /*forDict*/ )
} )
fmt . Fprintf ( w , "\n" )
return nil
}
func ( mod * modContext ) genDictType ( w io . Writer , name , comment string , properties [ ] * schema . Property ) error {
// Sort required props first.
props := make ( [ ] * schema . Property , len ( properties ) )
copy ( props , properties )
sort . Slice ( props , func ( i , j int ) bool {
pi , pj := props [ i ] , props [ j ]
switch {
case pi . IsRequired ( ) != pj . IsRequired ( ) :
return pi . IsRequired ( ) && ! pj . IsRequired ( )
default :
return pi . Name < pj . Name
}
2020-08-28 15:55:02 +00:00
} )
2020-08-19 08:16:47 +00:00
2024-06-18 15:24:42 +00:00
indent := " "
name = pythonCase ( name )
// TODO[pulumi/pulumi/issues/16408]
// Running mypy gets very slow when there are a lot of TypedDicts.
// https://github.com/python/mypy/issues/17231
// For now we only use the TypedDict types when using a typechecker
// other than mypy. For mypy we define the XXXArgsDict class as an alias
// to the type `Mapping[str, Any]`.
fmt . Fprintf ( w , "if not MYPY:\n" )
fmt . Fprintf ( w , "%sclass %sDict(TypedDict):\n" , indent , name )
indent += " "
if comment != "" {
printComment ( w , comment , indent )
}
for _ , prop := range props {
pname := PyName ( prop . Name )
ty := mod . typeString ( prop . Type , true /*input*/ , false /*acceptMapping*/ , true /*forDict*/ )
fmt . Fprintf ( w , "%s%s: %s\n" , indent , pname , ty )
if prop . Comment != "" {
printComment ( w , prop . Comment , indent )
}
}
if len ( props ) == 0 {
fmt . Fprintf ( w , "%spass\n" , indent )
}
indent = " "
fmt . Fprintf ( w , "elif False:\n" )
fmt . Fprintf ( w , "%s%sDict: TypeAlias = Mapping[str, Any]\n" , indent , name )
2020-08-19 08:16:47 +00:00
fmt . Fprintf ( w , "\n" )
return nil
}
2020-01-21 22:45:48 +00:00
func getPrimitiveValue ( value interface { } ) ( string , error ) {
v := reflect . ValueOf ( value )
if v . Kind ( ) == reflect . Interface {
v = v . Elem ( )
}
turn on the golangci-lint exhaustive linter (#15028)
Turn on the golangci-lint exhaustive linter. This is the first step
towards catching more missing cases during development rather than
in tests, or in production.
This might be best reviewed commit-by-commit, as the first commit turns
on the linter with the `default-signifies-exhaustive: true` option set,
which requires a lot less changes in the current codebase.
I think it's probably worth doing the second commit as well, as that
will get us the real benefits, even though we end up with a little bit
more churn. However it means all the `switch` statements are covered,
which isn't the case after the first commit, since we do have a lot of
`default` statements that just call `assert.Fail`.
Fixes #14601
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-17 16:50:41 +00:00
//nolint:exhaustive // Only a subset of types can have default values
2020-01-21 22:45:48 +00:00
switch v . Kind ( ) {
case reflect . Bool :
if v . Bool ( ) {
return "True" , nil
}
return "False" , nil
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 :
return strconv . FormatInt ( v . Int ( ) , 10 ) , nil
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 :
return strconv . FormatUint ( v . Uint ( ) , 10 ) , nil
case reflect . Float32 , reflect . Float64 :
return strconv . FormatFloat ( v . Float ( ) , 'f' , - 1 , 64 ) , nil
case reflect . String :
return fmt . Sprintf ( "'%s'" , v . String ( ) ) , nil
default :
2021-11-13 02:37:17 +00:00
return "" , fmt . Errorf ( "unsupported default value of type %T" , value )
2020-01-21 22:45:48 +00:00
}
}
2020-06-22 17:58:13 +00:00
func getConstValue ( cv interface { } ) ( string , error ) {
if cv == nil {
return "" , nil
}
return getPrimitiveValue ( cv )
}
2020-01-21 22:45:48 +00:00
func getDefaultValue ( dv * schema . DefaultValue , t schema . Type ) ( string , error ) {
defaultValue := ""
if dv . Value != nil {
v , err := getPrimitiveValue ( dv . Value )
if err != nil {
return "" , err
}
defaultValue = v
}
if len ( dv . Environment ) > 0 {
2020-07-15 20:10:52 +00:00
envFunc := "_utilities.get_env"
2020-01-21 22:45:48 +00:00
switch t {
case schema . BoolType :
2020-07-15 20:10:52 +00:00
envFunc = "_utilities.get_env_bool"
2020-01-21 22:45:48 +00:00
case schema . IntType :
2020-07-15 20:10:52 +00:00
envFunc = "_utilities.get_env_int"
2020-01-21 22:45:48 +00:00
case schema . NumberType :
2020-07-15 20:10:52 +00:00
envFunc = "_utilities.get_env_float"
2020-01-21 22:45:48 +00:00
}
envVars := fmt . Sprintf ( "'%s'" , dv . Environment [ 0 ] )
for _ , e := range dv . Environment [ 1 : ] {
envVars += fmt . Sprintf ( ", '%s'" , e )
}
if defaultValue == "" {
defaultValue = fmt . Sprintf ( "%s(%s)" , envFunc , envVars )
} else {
defaultValue = fmt . Sprintf ( "(%s(%s) or %s)" , envFunc , envVars , defaultValue )
}
}
return defaultValue , nil
}
2020-06-18 22:46:17 +00:00
func generateModuleContextMap ( tool string , pkg * schema . Package , info PackageInfo , extraFiles map [ string ] [ ] byte ) ( map [ string ] * modContext , error ) {
2021-05-06 18:18:14 +00:00
// determine whether to use the default Python package name
pyPkgName := info . PackageName
if pyPkgName == "" {
2023-12-12 12:19:42 +00:00
pyPkgName = "pulumi_" + strings . ReplaceAll ( pkg . Name , "-" , "_" )
2021-05-06 18:18:14 +00:00
}
2020-06-09 21:33:02 +00:00
// group resources, types, and functions into modules
2022-10-05 19:22:06 +00:00
// modules map will contain modContext entries for all modules in current package (pkg)
2020-01-21 22:45:48 +00:00
modules := map [ string ] * modContext { }
2022-12-07 12:03:41 +00:00
var getMod func ( modName string , p schema . PackageReference ) * modContext
getMod = func ( modName string , p schema . PackageReference ) * modContext {
2020-01-21 22:45:48 +00:00
mod , ok := modules [ modName ]
if ! ok {
mod = & modContext {
2021-10-01 18:33:02 +00:00
pkg : p ,
pyPkgName : pyPkgName ,
mod : modName ,
tool : tool ,
modNameOverrides : info . ModuleNameOverrides ,
compatibility : info . Compatibility ,
liftSingleValueMethodReturns : info . LiftSingleValueMethodReturns ,
2024-06-18 15:24:42 +00:00
inputTypes : info . InputTypes ,
}
if info . InputTypes == "" {
// TODO[pulumi/pulumi/16375]: Flip default to classes-and-dicts
mod . inputTypes = InputTypesSettingClasses
2020-01-21 22:45:48 +00:00
}
2022-12-07 12:03:41 +00:00
if modName != "" && codegen . PkgEquals ( p , pkg . Reference ( ) ) {
2020-01-21 22:45:48 +00:00
parentName := path . Dir ( modName )
2020-06-03 01:15:21 +00:00
if parentName == "." {
parentName = ""
2020-01-21 22:45:48 +00:00
}
2021-02-12 23:54:19 +00:00
parent := getMod ( parentName , p )
2021-04-29 20:08:22 +00:00
parent . addChild ( mod )
2020-01-21 22:45:48 +00:00
}
2021-02-12 23:54:19 +00:00
// Save the module only if it's for the current package.
// This way, modules for external packages are not saved.
2022-12-07 12:03:41 +00:00
if codegen . PkgEquals ( p , pkg . Reference ( ) ) {
2021-02-12 23:54:19 +00:00
modules [ modName ] = mod
}
2020-01-21 22:45:48 +00:00
}
return mod
}
2022-12-07 12:03:41 +00:00
getModFromToken := func ( tok string , p schema . PackageReference ) * modContext {
2021-02-12 23:54:19 +00:00
modName := tokenToModule ( tok , p , info . ModuleNameOverrides )
return getMod ( modName , p )
2020-06-03 01:15:21 +00:00
}
2020-01-21 22:45:48 +00:00
// Create the config module if necessary.
2020-06-11 18:52:26 +00:00
if len ( pkg . Config ) > 0 &&
2020-06-22 15:38:20 +00:00
info . Compatibility != kubernetes20 { // k8s SDK doesn't use config.
2022-12-07 12:03:41 +00:00
configMod := getMod ( "config" , pkg . Reference ( ) )
2020-08-20 18:07:09 +00:00
configMod . isConfig = true
2020-01-21 22:45:48 +00:00
}
2021-06-24 16:17:55 +00:00
visitObjectTypes ( pkg . Config , func ( t schema . Type ) {
2021-04-19 23:40:39 +00:00
if t , ok := t . ( * schema . ObjectType ) ; ok {
2022-12-07 12:03:41 +00:00
getModFromToken ( t . Token , t . PackageReference ) . details ( t ) . outputType = true
2020-09-23 19:39:25 +00:00
}
2020-08-19 08:16:47 +00:00
} )
// Find input and output types referenced by resources.
2020-04-30 21:16:56 +00:00
scanResource := func ( r * schema . Resource ) {
2022-12-07 12:03:41 +00:00
mod := getModFromToken ( r . Token , pkg . Reference ( ) )
2020-01-21 22:45:48 +00:00
mod . resources = append ( mod . resources , r )
2021-06-24 16:17:55 +00:00
visitObjectTypes ( r . Properties , func ( t schema . Type ) {
2020-09-23 19:39:25 +00:00
switch T := t . ( type ) {
case * schema . ObjectType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . outputType = true
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . resourceOutputType = true
2020-09-23 19:39:25 +00:00
}
2020-08-19 08:16:47 +00:00
} )
2021-06-24 16:17:55 +00:00
visitObjectTypes ( r . InputProperties , func ( t schema . Type ) {
2020-09-23 19:39:25 +00:00
switch T := t . ( type ) {
case * schema . ObjectType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . inputType = true
2020-08-19 08:16:47 +00:00
}
} )
if r . StateInputs != nil {
2021-06-24 16:17:55 +00:00
visitObjectTypes ( r . StateInputs . Properties , func ( t schema . Type ) {
2020-09-23 19:39:25 +00:00
switch T := t . ( type ) {
case * schema . ObjectType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . inputType = true
2020-09-23 19:39:25 +00:00
case * schema . ResourceType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . Resource . PackageReference )
2020-09-23 19:39:25 +00:00
}
2020-08-19 08:16:47 +00:00
} )
}
2020-01-21 22:45:48 +00:00
}
2020-04-30 21:16:56 +00:00
scanResource ( pkg . Provider )
2020-01-21 22:45:48 +00:00
for _ , r := range pkg . Resources {
2020-04-30 21:16:56 +00:00
scanResource ( r )
2020-01-21 22:45:48 +00:00
}
2020-08-19 08:16:47 +00:00
// Find input and output types referenced by functions.
2020-01-21 22:45:48 +00:00
for _ , f := range pkg . Functions {
2022-12-07 12:03:41 +00:00
mod := getModFromToken ( f . Token , f . PackageReference )
2021-07-07 13:57:18 +00:00
if ! f . IsMethod {
mod . functions = append ( mod . functions , f )
}
2020-08-19 08:16:47 +00:00
if f . Inputs != nil {
2021-06-24 16:17:55 +00:00
visitObjectTypes ( f . Inputs . Properties , func ( t schema . Type ) {
2020-09-23 19:39:25 +00:00
switch T := t . ( type ) {
case * schema . ObjectType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . inputType = true
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . plainType = true
2020-09-23 19:39:25 +00:00
case * schema . ResourceType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . Resource . PackageReference )
2020-09-23 19:39:25 +00:00
}
2020-08-19 08:16:47 +00:00
} )
}
2023-01-11 22:17:14 +00:00
var returnType * schema . ObjectType
if f . ReturnType != nil {
if objectType , ok := f . ReturnType . ( * schema . ObjectType ) ; ok && objectType != nil {
returnType = objectType
}
}
if returnType != nil {
visitObjectTypes ( returnType . Properties , func ( t schema . Type ) {
2020-09-23 19:39:25 +00:00
switch T := t . ( type ) {
case * schema . ObjectType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . outputType = true
getModFromToken ( T . Token , T . PackageReference ) . details ( T ) . plainType = true
2020-09-23 19:39:25 +00:00
case * schema . ResourceType :
2022-12-07 12:03:41 +00:00
getModFromToken ( T . Token , T . Resource . PackageReference )
2020-09-23 19:39:25 +00:00
}
2020-08-19 08:16:47 +00:00
} )
}
}
// Find nested types.
for _ , t := range pkg . Types {
2020-11-25 05:43:32 +00:00
switch typ := t . ( type ) {
case * schema . ObjectType :
2022-12-07 12:03:41 +00:00
mod := getModFromToken ( typ . Token , typ . PackageReference )
2020-11-25 05:43:32 +00:00
d := mod . details ( typ )
2020-08-19 08:16:47 +00:00
if d . inputType || d . outputType {
2020-11-25 05:43:32 +00:00
mod . types = append ( mod . types , typ )
2020-08-19 08:16:47 +00:00
}
2020-11-25 05:43:32 +00:00
case * schema . EnumType :
2021-11-16 23:53:28 +00:00
if ! typ . IsOverlay {
2022-12-07 12:03:41 +00:00
mod := getModFromToken ( typ . Token , pkg . Reference ( ) )
2021-11-16 23:53:28 +00:00
mod . enums = append ( mod . enums , typ )
}
2020-11-25 05:43:32 +00:00
default :
continue
2020-08-19 08:16:47 +00:00
}
2020-01-21 22:45:48 +00:00
}
2020-06-18 22:46:17 +00:00
// Add python source files to the corresponding modules. Note that we only add the file names; the contents are
// still laid out manually in GeneratePackage.
for p := range extraFiles {
2020-06-17 02:27:06 +00:00
if path . Ext ( p ) != ".py" {
continue
}
modName := path . Dir ( p )
if modName == "/" || modName == "." {
modName = ""
}
2022-12-07 12:03:41 +00:00
mod := getMod ( modName , pkg . Reference ( ) )
2020-06-17 02:27:06 +00:00
mod . extraSourceFiles = append ( mod . extraSourceFiles , p )
2020-06-18 22:46:17 +00:00
}
2022-10-05 19:22:06 +00:00
// Setup modLocator so that mod.typeDetails finds the right
// modContext for every ObjectType.
modLocator := & modLocator {
objectTypeMod : func ( t * schema . ObjectType ) * modContext {
2022-12-07 12:03:41 +00:00
if ! codegen . PkgEquals ( t . PackageReference , pkg . Reference ( ) ) {
2022-10-05 19:22:06 +00:00
return nil
}
2022-12-07 12:03:41 +00:00
return getModFromToken ( t . Token , t . PackageReference )
2022-10-05 19:22:06 +00:00
} ,
}
for _ , mod := range modules {
mod . modLocator = modLocator
}
2020-06-18 22:46:17 +00:00
return modules , nil
}
// LanguageResource is derived from the schema and can be used by downstream codegen.
type LanguageResource struct {
* schema . Resource
Name string // The resource name (e.g. Deployment)
Package string // The package name (e.g. pulumi_kubernetes.apps.v1)
}
// LanguageResources returns a map of resources that can be used by downstream codegen. The map
// key is the resource schema token.
func LanguageResources ( tool string , pkg * schema . Package ) ( map [ string ] LanguageResource , error ) {
resources := map [ string ] LanguageResource { }
if err := pkg . ImportLanguages ( map [ string ] schema . Language { "python" : Importer } ) ; err != nil {
return nil , err
}
info , _ := pkg . Language [ "python" ] . ( PackageInfo )
modules , err := generateModuleContextMap ( tool , pkg , info , nil )
if err != nil {
return nil , err
}
for modName , mod := range modules {
if modName == "" {
continue
}
for _ , r := range mod . resources {
2021-11-12 00:00:03 +00:00
if r . IsOverlay {
// This resource code is generated by the provider, so no further action is required.
continue
}
2023-02-23 20:51:11 +00:00
packagePath := strings . ReplaceAll ( modName , "/" , "." )
2020-06-18 22:46:17 +00:00
lr := LanguageResource {
Resource : r ,
Package : packagePath ,
Name : pyClassName ( tokenToName ( r . Token ) ) ,
}
resources [ r . Token ] = lr
}
}
return resources , nil
}
func GeneratePackage ( tool string , pkg * schema . Package , extraFiles map [ string ] [ ] byte ) ( map [ string ] [ ] byte , error ) {
// Decode python-specific info
if err := pkg . ImportLanguages ( map [ string ] schema . Language { "python" : Importer } ) ; err != nil {
return nil , err
}
info , _ := pkg . Language [ "python" ] . ( PackageInfo )
2020-01-21 22:45:48 +00:00
2020-06-18 22:46:17 +00:00
modules , err := generateModuleContextMap ( tool , pkg , info , extraFiles )
if err != nil {
return nil , err
}
2021-05-06 18:18:14 +00:00
pkgName := info . PackageName
if pkgName == "" {
pkgName = pyPack ( pkg . Name )
}
2022-11-09 19:49:59 +00:00
files := codegen . Fs { }
2020-06-18 22:46:17 +00:00
for p , f := range extraFiles {
2022-11-09 19:49:59 +00:00
files . Add ( filepath . Join ( pkgName , p ) , f )
2020-01-21 22:45:48 +00:00
}
2020-06-17 02:27:06 +00:00
2020-01-21 22:45:48 +00:00
for _ , mod := range modules {
if err := mod . gen ( files ) ; err != nil {
return nil , err
}
}
2021-12-15 18:41:44 +00:00
// Generate pulumi-plugin.json
2021-12-02 02:07:23 +00:00
plugin , err := genPulumiPluginFile ( pkg )
if err != nil {
return nil , err
2020-12-04 03:22:16 +00:00
}
2022-11-09 19:49:59 +00:00
files . Add ( filepath . Join ( pkgName , "pulumi-plugin.json" ) , plugin )
2020-12-04 03:22:16 +00:00
2023-05-01 01:22:20 +00:00
// Next, emit the package metadata (setup.py).
2023-09-06 13:08:42 +00:00
if ! info . PyProject . Enabled {
setup , err := genPackageMetadata ( tool , pkg , pkgName , info . Requires , info . PythonRequires )
if err != nil {
return nil , err
}
files . Add ( "setup.py" , [ ] byte ( setup ) )
2020-01-21 22:45:48 +00:00
}
2023-05-01 01:22:20 +00:00
// Finally, if pyproject.toml generation is enabled, generate
// this file and emit it as well.
if info . PyProject . Enabled {
project , err := genPyprojectTOML (
2023-05-02 17:20:28 +00:00
tool , pkg , pkgName ,
2023-05-01 01:22:20 +00:00
)
if err != nil {
return nil , err
}
files . Add ( "pyproject.toml" , [ ] byte ( project ) )
}
2020-01-21 22:45:48 +00:00
return files , nil
}
2023-05-01 01:22:20 +00:00
func genPyprojectTOML ( tool string ,
pkg * schema . Package ,
pyPkgName string ,
) ( string , error ) {
2023-05-02 15:48:45 +00:00
// First, create a Writer for everything in pyproject.toml
w := & bytes . Buffer { }
// Create an empty toml manifest.
2023-05-02 17:20:28 +00:00
schema := new ( PyprojectSchema )
schema . Project = new ( Project )
2023-05-02 15:48:45 +00:00
// Populate the fields.
schema . Project . Name = & pyPkgName
2023-05-02 17:20:28 +00:00
schema . Project . Keywords = pkg . Keywords
// Setting dependencies fails if the deps we provide specify
// an invalid Pulumi package version as a dep.
err := setDependencies ( schema , pkg )
if err != nil {
return "" , err
}
2023-05-02 15:48:45 +00:00
2023-05-02 17:20:28 +00:00
// This sets the minimum version of Python.
setPythonRequires ( schema , pkg )
2023-05-02 15:48:45 +00:00
2023-05-02 17:20:28 +00:00
// Set the project's URLs
schema . Project . URLs = mapURLs ( pkg )
2023-05-02 16:46:59 +00:00
2023-05-02 17:20:28 +00:00
// • Description and License: These fields are populated the same
// way as in setup.py.
description := sanitizePackageDescription ( pkg . Description )
schema . Project . Description = & description
schema . Project . License = & License {
Text : pkg . License ,
}
2023-05-02 15:48:45 +00:00
2023-05-02 17:20:28 +00:00
// • Next, we set the version field.
// A Version of 0.0.0 is typically overridden elsewhere with sed
// or a similar tool.
2023-05-02 15:48:45 +00:00
version := "0.0.0"
info , ok := pkg . Language [ "python" ] . ( PackageInfo )
if pkg . Version != nil && ok && info . RespectSchemaVersion {
2024-01-30 15:02:59 +00:00
version = PypiVersion ( * pkg . Version )
2023-05-02 15:48:45 +00:00
}
2024-07-16 10:55:38 +00:00
if pkg . SupportPack || pkg . Parameterization != nil {
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
if pkg . Version == nil {
return "" , errors . New ( "package version is required" )
}
version = PypiVersion ( * pkg . Version )
}
2023-05-02 15:48:45 +00:00
schema . Project . Version = & version
2023-05-02 17:20:28 +00:00
// • Set the path to the README.
readme := "README.md"
schema . Project . README = & readme
2023-09-06 13:08:42 +00:00
// Populate build extensions as follows:
//
// [build-system]
// requires = ["setuptools>=61.0"]
// build-backend = "setuptools.build_meta"
// [tool.setuptools.package-data]
// pulumi_kubernetes = ["py.typed", "pulumi-plugin.json"]
//
// This ensures that `python -m build` can proceed without `setup.py` present, while still
// including the required files `py.typed` and `pulumi-plugin.json` in the distro.
schema . BuildSystem = & BuildSystem {
Requires : [ ] string { "setuptools>=61.0" } ,
BuildBackend : "setuptools.build_meta" ,
}
schema . Tool = map [ string ] interface { } {
"setuptools" : map [ string ] interface { } {
"package-data" : map [ string ] interface { } {
* schema . Project . Name : [ ] string {
"py.typed" ,
"pulumi-plugin.json" ,
} ,
} ,
} ,
}
2023-05-02 17:20:28 +00:00
// • Marshal the data into TOML format.
err = toml . NewEncoder ( w ) . Encode ( schema )
2023-09-06 13:08:42 +00:00
2023-05-02 17:20:28 +00:00
return w . String ( ) , err
2023-05-02 15:48:45 +00:00
}
2023-05-02 17:20:28 +00:00
// mapURLs creates a map between the name of the URL and the URL itself.
// Currently, only two URLs are supported: the project "Homepage" and the
// project "Repository", which are the corresponding map keys.
func mapURLs ( pkg * schema . Package ) map [ string ] string {
urls := map [ string ] string { }
if homepage := pkg . Homepage ; homepage != "" {
urls [ "Homepage" ] = homepage
}
if repo := pkg . Repository ; repo != "" {
urls [ "Repository" ] = repo
}
return urls
2023-05-02 15:48:45 +00:00
}
2023-05-02 17:20:28 +00:00
// setPythonRequires adds a minimum version of Python required to run this package.
// It falls back to the default version supported by Pulumi if the user hasn't provided
// one in the schema.
func setPythonRequires ( schema * PyprojectSchema , pkg * schema . Package ) {
info := pkg . Language [ "python" ] . ( PackageInfo )
// Start with the default, and replace it if the user provided
// a specific version.
minPython := defaultMinPythonVersion
if userPythonVersion , err := minimumPythonVersion ( info ) ; err == nil {
minPython = userPythonVersion
2023-05-02 15:48:45 +00:00
}
2023-05-02 17:20:28 +00:00
schema . Project . RequiresPython = & minPython
2023-05-01 01:22:20 +00:00
}
2023-05-02 16:46:59 +00:00
// setDependencies mutates the pyproject schema adding the dependencies to the
// list in lexical order.
2023-05-02 17:20:28 +00:00
func setDependencies ( schema * PyprojectSchema , pkg * schema . Package ) error {
requires := map [ string ] string { }
if info , ok := pkg . Language [ "python" ] . ( PackageInfo ) ; ok {
2024-06-18 15:24:42 +00:00
requires = make ( map [ string ] string , len ( info . Requires ) )
for k , v := range info . Requires {
requires [ k ] = v
}
if info . InputTypes == InputTypesSettingClassesAndDicts {
requires [ "typing-extensions" ] = ">=4.11; python_version < \"3.11\""
}
2023-05-02 17:20:28 +00:00
}
2023-05-02 16:46:59 +00:00
deps , err := calculateDeps ( requires )
if err != nil {
return err
}
for _ , dep := range deps {
// Append the dep constraint to the end of the dep name.
// e.g. pulumi>=3.50.1
depConstraint := fmt . Sprintf ( "%s%s" , dep [ 0 ] , dep [ 1 ] )
schema . Project . Dependencies = append ( schema . Project . Dependencies , depConstraint )
}
return nil
}
2024-06-24 13:57:57 +00:00
// Require the SDK to fall within the same major version.
var MinimumValidSDKVersion = ">=3.0.0,<4.0.0"
2023-05-02 16:46:59 +00:00
// ensureValidPulumiVersion ensures that the Pulumi SDK has an entry.
2023-05-02 17:20:28 +00:00
// It accepts a list of dependencies
2023-05-02 16:46:59 +00:00
// as provided in the package schema, and validates whether
// this list correctly includes the Pulumi Python package.
// It returns a map that correctly specifies the dependency.
// This function does not modify the argument. Instead, it returns
// a copy of the original map, except that the `pulumi` key is guaranteed to have
// a valid value.
// This function returns an error if the provided Pulumi version fails to
// validate.
func ensureValidPulumiVersion ( requires map [ string ] string ) ( map [ string ] string , error ) {
deps := map [ string ] string { }
2024-06-24 13:57:57 +00:00
// Special case: if the map is empty, we return just pulumi with the minimum version constraint.
2023-05-26 18:28:10 +00:00
if len ( requires ) == 0 {
result := map [ string ] string {
2024-06-24 13:57:57 +00:00
"pulumi" : MinimumValidSDKVersion ,
2023-05-26 18:28:10 +00:00
}
return result , nil
}
// If the pulumi dep is missing, we require it to fall within
// our major version constraint.
2023-05-02 16:46:59 +00:00
if pulumiDep , ok := requires [ "pulumi" ] ; ! ok {
2024-06-24 13:57:57 +00:00
deps [ "pulumi" ] = MinimumValidSDKVersion
2023-05-02 16:46:59 +00:00
} else {
// Since a value was provided, we check to make sure it's
// within an acceptable version range.
// We expect a specific pattern of ">=version,<version" here.
matches := requirementRegex . FindStringSubmatch ( pulumiDep )
if len ( matches ) != 2 {
return nil , fmt . Errorf ( "invalid requirement specifier \"%s\"; expected \">=version1,<version2\"" , pulumiDep )
}
lowerBound , err := pep440VersionToSemver ( matches [ 1 ] )
if err != nil {
2023-12-20 15:54:06 +00:00
return nil , fmt . Errorf ( "invalid version for lower bound: %w" , err )
2023-05-02 16:46:59 +00:00
}
if lowerBound . LT ( oldestAllowedPulumi ) {
return nil , fmt . Errorf ( "lower version bound must be at least %v" , oldestAllowedPulumi )
}
2023-05-26 18:28:10 +00:00
// The provided Pulumi version is valid, so we're copy it into
// the new map.
deps [ "pulumi" ] = pulumiDep
2023-05-02 16:46:59 +00:00
}
// Copy the rest of the dependencies listed into deps.
for k , v := range requires {
if k == "pulumi" {
continue
}
deps [ k ] = v
}
return deps , nil
}
// calculateDeps determines the dependencies of this project
// and orders them lexigraphical.
// This function returns a slice of tuples, where the first element
// of each tuple is the name of the dependency, and the second element
// is the dependency's version constraint.
// This function returns an error if the version of Pulumi listed as a
// dep fails to validate.
func calculateDeps ( requires map [ string ] string ) ( [ ] [ 2 ] string , error ) {
var err error
2023-06-28 16:02:04 +00:00
result := slice . Prealloc [ [ 2 ] string ] ( len ( requires ) )
2023-05-02 16:46:59 +00:00
if requires , err = ensureValidPulumiVersion ( requires ) ; err != nil {
return nil , err
}
// Collect all of the names into an array, including
// two extras that we hardcode.
// NB: I have no idea why we hardcode these values here. Because we
// access the map later, they MUST already be in the map,
// or else we'd be writing nil to the file, but since we append
// them here, I'd expect them to show up twice in the output file.
deps := [ ] string {
"semver>=2.8.1" ,
"parver>=0.2.1" ,
}
for dep := range requires {
deps = append ( deps , dep )
}
sort . Strings ( deps )
for _ , dep := range deps {
next := [ 2 ] string {
dep , requires [ dep ] ,
}
result = append ( result , next )
}
return result , nil
}
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
//go:embed utilities.py
var utilitiesFile string