jsii icon indicating copy to clipboard operation
jsii copied to clipboard

[info] Example automatically generated

Open rix0rrr opened this issue 4 years ago • 14 comments

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).

rix0rrr avatar Sep 27 '19 09:09 rix0rrr

The initial PR is here: https://github.com/aws/jsii/pull/827

rix0rrr avatar Sep 27 '19 19:09 rix0rrr

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

I found the issue above under the FileAssetLocation for python :-S

brainstorm avatar Oct 24 '19 07:10 brainstorm

Good catch, that needs to be sorted as well.

rix0rrr avatar Nov 20 '19 09:11 rix0rrr

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

curzona avatar Dec 06 '19 02:12 curzona

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());

drissamri avatar Aug 02 '20 12:08 drissamri

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

WhyNotHugo avatar Mar 10 '21 19:03 WhyNotHugo

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? 😇

rix0rrr avatar Jun 04 '21 12:06 rix0rrr

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

# 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)

ryparker avatar Jun 24 '21 18:06 ryparker

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))

bl-ue avatar Jul 12 '21 12:07 bl-ue

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 🤔)

bl-ue avatar Jul 12 '21 12:07 bl-ue

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.

tvb avatar Aug 18 '21 09:08 tvb

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.

arcrank avatar Nov 03 '21 20:11 arcrank

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

peterwoodworth avatar Nov 10 '21 21:11 peterwoodworth

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(),


                ),
            ),

heitorlessa avatar Sep 02 '22 17:09 heitorlessa