jsii
jsii copied to clipboard
[info] Example automatically generated
In order to provide CDK users with code samples in every language, we automatically translate snippets of TypeScript sample code into other languages.
You can recognize these translated example blocks by them starting with a text similar to the following:
# Example automatically generated. See https://...
This translation is currently experimental, and we'd like you to let us know how this is working out for you. Please comment on this issue if there's something you would like to discuss.
Examples we couldn't compile
We try compiling all the samples before translating them. This is necessary to extract type information that is necessary to render the correct translation of non-TypeScript languages.
However, compilation of sample code requires some setup that we haven't been able to do for all of our code samples yet. That means that many samples are translated without complete type information, and may hence be incorrect. You can recognize them by starting with a comment similar to:
# Example automatically generated without compilation. See https://...
If you encounter issues with some of these samples that are caused by its lack of compilation, we'd love it if you could help out by submitting a Pull Request to make the samples compile! See the following section to figure out what to expect.
Translation mistakes in uncompiled samples
The most common mistake in uncompiled samples is going to be around Structs, which are pure data objects passed to constructors and methods. They are typically called something like FooProps
or FooOptions
, and in samples they appear something like this:
new SomeClass('param', {
prop1: 'value1',
prop2: 'value2'
})
MISTAKE 1: is it a struct or a map?
Mistake 1 the translator may make in translating the preceding code example without having any type information available, is that it can't know whether the argument to new SomeClass
is intended to be of type {[key: string]: string}
(i.e., a map of string
to string
), or of type interface SomeClassProps
(i.e., a struct).
The caller side for both types looks exactly the same in TypeScript, so the translator can't distinguish between them, and so it is forced to guess.
In most cases uses like this will be structs, so that's what the translator will guess given no other information. However, some classes legitimately take a map as argument (for example, GenericLinuxImage
takes a map of region-to-ami), and in those cases the translator will have guessed wrong and produce an incorrect translation.
N.B.: The Java translator will prefer to guess that an untyped value is a map, rather than a struct.
MISTAKE 2: what is the struct's name?
When the translator guesses that something is a struct, it needs to translate it. In a language like Python, structs get translated into keyword arguments and so they don't need names. In other languages like Java and C#, structs get translated into data classes, and you need their name to instantiate them. Unfortunately, the name of the structs doesn't appear in the code sample and since we have no type information we can't look it up.
In those cases, the translator will generate a placeholder name called Struct
, with the understanding that you will need to go and look up the actual name when using this code. For example, the C# translator will have translated the above snippet into:
new SomeClass('param', new Struct {
Prop1 = "value1",
Prop2 = "value2"
})
You will need to go and look at the documentation of SomeClass
to figure out that new Struct
actually needs to be new SomeClassProps
, or something to that effect.
How to make samples compile successfully
We'd rather have all samples compile successfully. To achieve that, modules need some boilerplate added to make it possible to compile the examples. Typically, this boilerplate file will just import some names or set up some variables. This is called a fixture.
For example, making our running example compilable would look something like this.
Add a file to the package called rosetta/my-fixture.ts-fixture
, and put the following into it:
import { SomeClass } from 'my-library';
/// here
And update the example to look like this:
```ts fixture=my-fixture
new SomeClass('param', {
prop1: 'value1',
prop2: 'value2'
})
```
The example code will be inserted into the fixture file at the location marked by /// here
, and the resulting complete file should compile.
For more information, see the "Examples" section of aws-cdk's Contribution Guide or the "Fixtures" section of the rosetta tool.
Known Issues
The following are known issues with the translation tool:
Language keywords are not respected: Sometimes TypeScript code uses identifiers which are reserved keywords in other languages. The translated code may copy those identifiers without modification, leading to code that won't compile (example: import aws_cdk.aws_lambda as lambda
won't actually work in Python as lambda
is a reserved keyword).
Single values are interpreted as code snippets: The @example
doc comment directive is used both for code snippets, as well as exemplar values for properties. The translator tries to translate the sample values, which will maul them (example).
The initial PR is here: https://github.com/aws/jsii/pull/827
How is this effort going? My team would absolutely love working python examples on the docs. For now we are relying on the excellent aws-cdk-examples for python, but it would be nice to have working examples inlined in the docs:
![Skärmavbild 2019-10-24 kl 18 18 11](https://user-images.githubusercontent.com/175587/67462129-a8d59480-f68a-11e9-9c4d-ee571f4ac09b.png)
I found the issue above under the FileAssetLocation for python :-S
Good catch, that needs to be sorted as well.
lambda
is a keyword in python and can't use used as an import alias.
import aws_cdk.aws_lambda as lambda
Ex: https://pypi.org/project/aws-cdk.aws-lambda/
should be
import aws_cdk.aws_lambda as lambda_
Like the aws-sdk-examples do. Ex: https://github.com/aws-samples/aws-cdk-examples/blob/master/python/lambda-cron/app.py
The Java examples seem to have a lot of issues as well with Classes/Builders and imports: https://docs.aws.amazon.com/cdk/api/latest/java/index.html?software/amazon/awscdk/services/dynamodb/package-summary.html
Table table = new Table(this, "Table", new TableProps()
.partitionKey(new Attribute().name("id").type(dynamodb.AttributeType.getSTRING())));
should be
Table table = new Table(this, "Table", TableProps.builder()
.partitionKey(Attribute.builder()
.name("id")
.type(AttributeType.STRING)
.build())
.build());
Static methods should not have self
as a first parameter:
arn = "arn:aws:..."
certificate = Certificate.from_certificate_arn(self, "Certificate", arn)
https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_certificatemanager/README.html#importing
Sorry to do this to your @RomainMuller but since you're working on Rosetta right now anyway, might as well work in these bug reports at the same time? 😇
Leaving a note here that I recently came across an issue which involved a python example that does not compile and can lead to some confusion.
# Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826
from aws_cdk.core import CustomResource
import aws_cdk.aws_logs as logs
import aws_cdk.aws_iam as iam
import aws_cdk.custom_resources as cr
on_event = lambda_.Function(self, "MyHandler")
my_role = iam.Role(self, "MyRole")
my_provider = cr.Provider(self, "MyProvider",
on_event_handler=on_event,
is_complete_handler=is_complete, # optional async "waiter"
log_retention=logs.RetentionDays.ONE_DAY, # default is INFINITE
role=my_role
)
CustomResource(self, "Resource1", service_token=my_provider.service_token)
CustomResource(self, "Resource2", service_token=my_provider.service_token)
This example requires some modifications in order to compile. Here's is a modified version of the above example that works. (Given the 2 lambda handlers are provided in external files)
from aws_cdk import core as cdk
import aws_cdk.aws_lambda as lambda_
import aws_cdk.aws_logs as logs
import aws_cdk.aws_iam as iam
import aws_cdk.custom_resources as cr
class FooStack(cdk.Construct):
def __init__(self, scope: cdk.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
with open("src/lambda_handlers/on_event.py", encoding="utf-8") as fp:
on_event_code_body = fp.read()
on_event = lambda_.Function(self, "on_event",
runtime=lambda_.Runtime.PYTHON_3_7,
handler="index.on_event",
code=lambda_.InlineCode(on_event_code_body),
)
with open("src/lambda_handlers/is_complete.py", encoding="utf-8") as fp:
is_complete_code_body = fp.read()
is_complete = lambda_.Function(self, "is_complete",
runtime=lambda_.Runtime.PYTHON_3_7,
handler="index.is_complete",
code=lambda_.InlineCode(is_complete_code_body),
)
my_role = iam.Role(self, "MyRole",
assumed_by=iam.ServicePrincipal("lambda.amazonaws.com")
)
my_provider = cr.Provider(self, "MyProvider",
on_event_handler=on_event,
is_complete_handler=is_complete, # optional async "waiter"
log_retention=logs.RetentionDays.ONE_DAY, # default is INFINITE
role=my_role
)
cdk.CustomResource(self, "Resource1", service_token=my_provider.service_token)
cdk.CustomResource(self, "Resource2", service_token=my_provider.service_token)
Bug with Python translation of JavaScript instanceof
keyword (aws-cdk/aws-s3
, https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_s3/README.html#encryption):
assert(bucket.encryption_key instanceof kms.Key)
Python doesn't have the instanceof
keyword. The correct form is:
assert(isinstance(bucket.encryption_key, kms.Key))
Bug with Python translation of JavaScript ??
(nullish-coalescing operator) (aws-cdk/core
, https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.core/README.html)
uniqueid = "GloballyUniqueIdForSingleton"return stack.node.try_find_child(uniqueid) ?? sns.Topic(stack, uniqueid)
It should be
uniqueid = "GloballyUniqueIdForSingleton"
return stack.node.try_find_child(uniqueid) or sns.Topic(stack, uniqueid)
(Also, there's a missing newline between the string literal and the return
keyword 🤔)
Just noted:
# Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826
assets.Asset(self, "BundledAsset",
path="/path/to/asset",
bundling={
"local": {
def try_bundle(self, output_dir, options):
if can_run_locally: return Truereturn False
},
# Docker bundling fallback
"image": DockerImage.from_registry("alpine"),
"entrypoint": ["/bin/sh", "-c"],
"command": ["bundle"]
}
)
if can_run_locally: return Truereturn False
Looks wrong to me.
Another instance where JSII doesn't play nice with python, in aws-cdk python docs.
product = servicecatalog.CloudFormationProduct(self, "MyFirstProduct",
product_name="My Product",
owner="Product Owner",
product_versions=[{
"product_version_name": "v1",
"cloud_formation_template": servicecatalog.CloudFormationTemplate.from_url("https://raw.githubusercontent.com/awslabs/aws-cloudformation-templates/master/aws/services/ServiceCatalog/Product.yaml")
}, {
"product_version_name": "v2",
"cloud_formation_template": servicecatalog.CloudFormationTemplate.from_asset(path.join(__dirname, "development-environment.template.json"))
}
]
)
but when actually running cdk commands the field cloud_formation_template
is actually cloudFormationTemplate
.
You would get error
jsii.errors.JSIIError: Missing required properties for @aws-cdk/aws-servicecatalog.CloudFormationProductVersion: cloudFormationTemplate
Seems odd to me since most python translations are snake case.
Another issue here in the docs
Specifically, FlinkApplicationProperties
doesn't work the way the documentation describes here
property_groups=PropertyGroups(
FlinkApplicationProperties={
"input_stream_name": "my-input-kinesis-stream",
"output_stream_name": "my-output-kinesis-stream"
}
)
Mimic this example and you will fail with this error:
__init__() got an unexpected keyword argument 'FlinkApplicationProperties'
More information can be found about this issue in this comment here
I've just hit this when trying to use ILocalBundling
. I couldn't find the docs so I kept searching for tests in jsii
, and the closest I found was closed https://github.com/aws/aws-cdk/issues/17928.
I'd be more than happy to contribute to the docs if I manage to find a solution.
Error: Failed to bundle asset testV39-lambda-layer-afca3bb3-4495-4f22-b8ac-4f345b9b25ec/aws-lambda-powertools-e2e-test/Code/Stage, bundle output is located at /Users/lessa/DEV/aws-lambda-powertools-python/tests/e2e/tracer/cdk.out/asset.f0f2e3bad29d71e35a9ec7740809279d1a3f36af4f1c905b438aacd6fc2bcf12-error:
TypeError: options.local?.tryBundle is not a function
@jsii.implements(ILocalBundling)
class LocalPipInstall:
@staticmethod
@jsii.member(jsii_name="tryBundle")
def try_bundle(output_dir: str, **kwargs) -> bool:
install_command = f'pip install .\[pydantic\] --platform manylinux1_x86_64 --only-binary=:all: -t {output_dir}'
subprocess.run(install_command, shell=True)
return True
# try this too to no avail. Omitting staticmethod returns a Callable signature error
@staticmethod
@jsii.member(jsii_name="tryBundle")
def tryBundle(output_dir: str, **kwargs) -> bool:
install_command = f'pip install .\[pydantic\] --platform manylinux1_x86_64 --only-binary=:all: -t {output_dir}'
subprocess.run(install_command, shell=True)
return True
....
code=Code.from_asset(
path=str(SOURCE_CODE_ROOT_PATH),
bundling=BundlingOptions(
image=DockerImage.from_build(
str(Path(__file__).parent),
build_args={"IMAGE": PythonVersion[PYTHON_RUNTIME_VERSION].value["image"]},
),
command=["bash", "-c", " && ".join(build_commands)],
# Here
local=LocalPipInstall(),
),
),