Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python class (Interface) is generated out of order #4426

Open
automartin5000 opened this issue Feb 13, 2024 · 13 comments
Open

Python class (Interface) is generated out of order #4426

automartin5000 opened this issue Feb 13, 2024 · 13 comments
Labels
bug This issue is a bug. p1

Comments

@automartin5000
Copy link

Describe the bug

I have a CDK construct library which has been working fine. But suddenly, I think possibly with the addition of named exports, I'm getting an error when attempting to import a construct when it attempts to reference one of the interfaces in a Python CDK app. Looking at the generated Python code, I noticed that the reference occurs before the class is defined.

Here's an example of how the interfaces are defined. Note: These are not really empty.

import { StackProps } from "aws-cdk-lib";

export interface BaseProps {}

export interface SomeAdditionalProps extends SomeProps {} //This may not be relevant

export interface AllTheProps extends StackProps, BaseProps {}

And here's the generated code:

@jsii.data_type(
    jsii_type="@scope/package/moduleName.SomeAdditionalProps",
    jsii_struct_bases=[BaseProps],
    name_mapping={}
)

class SomeAdditionalProps(BaseProps):
    def __init__()....

## A couple hundred lines later
class BaseProps:

Stack trace:

line 133, in <module>
    jsii_struct_bases=[BaseProps],
                       ^^^^^^^^^^^^^^^
NameError: name 'BaseProps' is not defined. Did you mean: ....

Expected Behavior

CDK Synth correctly

Current Behavior

Synth error (See above)

Reproduction Steps

See above code

Possible Solution

No response

Additional Information/Context

No response

SDK version used

5.3.12

Environment details (OS name and version, etc.)

Mac OS Sonoma

@automartin5000 automartin5000 added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Feb 13, 2024
@automartin5000
Copy link
Author

So based on my testing, what seems to be happening is JSII is generating classes in alphabetical order (is that correct?). But if Class A depends on Class B, then Class B is not yet defined and it throws an error. So I was able to work around this problem by renaming Class B to something like Class AppB so it went before the original Class A.

Does this seem like expected behavior? If so, I would expect that some dependency resolution is needed.

@automartin5000
Copy link
Author

I just ran into this again today. I can't possibly be the only person experiencing this?

@mrgrain mrgrain added p1 and removed needs-triage This issue or PR still needs to be triaged. labels Apr 9, 2024
@otaviomacedo otaviomacedo self-assigned this Apr 23, 2024
@otaviomacedo
Copy link
Contributor

Hi, @automartin5000. I can't reproduce the bug based on the information you provided. Is there a complete example you can share?

JSII is generating classes in alphabetical order

jsii generates classes in topological order, where class A is a predecessor of class B if A depends on B in some way (being a subclass is one example). This is done precisely to avoid this issue. The root cause must be something else.

@otaviomacedo otaviomacedo added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label May 15, 2024
Copy link
Contributor

This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.

@github-actions github-actions bot added the closing-soon This issue will automatically close in 4 days unless further comments are made. label May 16, 2024
@automartin5000
Copy link
Author

Thanks for the response @otaviomacedo. I'll try to recreate it this week or next

@github-actions github-actions bot removed closing-soon This issue will automatically close in 4 days unless further comments are made. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels May 17, 2024
@automartin5000
Copy link
Author

Sorry for the delay, here's a clean example:

constructs.ts

import { Construct } from "constructs";
import { AProps } from "./interfaces";

export class TestConstruct extends Construct {
  constructor(scope: Construct, id: string, props: AProps) {
    console.log(`Initialized prop: ${props.testProp}`);
    super(scope, id);
  }
}

interfaces.ts

export interface AProps extends BProps {}

export interface BProps {
  readonly testProp: string;
}

Python app code:

from <construct_library>.test_construct import AProps
from aws_cdk import App

class TestConstruct:
    def __init__(self, scope, id, props: AProps):
        super().__init__(scope, id, props)

test = TestConstruct(App(), 'TestConstruct', AProps(test_prop='test'))

Error:

_init__.py", line 20, in <module>
    jsii_struct_bases=[BProps],
                       ^^^^^^
NameError: name 'BProps' is not defined

Full JSII generated code (anonymized):

import abc
import builtins
import datetime
import enum
import typing

import jsii
import publication
import typing_extensions

from typeguard import check_type

from .._jsii import *

import constructs as _constructs_77d1e7e8


@jsii.data_type(
    jsii_type="<construct_library>.testConstruct.AProps",
    jsii_struct_bases=[BProps],
    name_mapping={"test_prop": "testProp"},
)
class AProps(BProps):
    def __init__(self, *, test_prop: builtins.str) -> None:
        '''
        :param test_prop: 
        '''
        if __debug__:
            type_hints = typing.get_type_hints(_typecheckingstub__6c50750a3eed61a5e3bf6110860acdf626b4cbe1cbe7b2cee25f235971070407)
            check_type(argname="argument test_prop", value=test_prop, expected_type=type_hints["test_prop"])
        self._values: typing.Dict[builtins.str, typing.Any] = {
            "test_prop": test_prop,
        }

    @builtins.property
    def test_prop(self) -> builtins.str:
        result = self._values.get("test_prop")
        assert result is not None, "Required property 'test_prop' is missing"
        return typing.cast(builtins.str, result)

    def __eq__(self, rhs: typing.Any) -> builtins.bool:
        return isinstance(rhs, self.__class__) and rhs._values == self._values

    def __ne__(self, rhs: typing.Any) -> builtins.bool:
        return not (rhs == self)

    def __repr__(self) -> str:
        return "AProps(%s)" % ", ".join(
            k + "=" + repr(v) for k, v in self._values.items()
        )


@jsii.data_type(
    jsii_type="<construct_library>.testConstruct.BProps",
    jsii_struct_bases=[],
    name_mapping={"test_prop": "testProp"},
)
class BProps:
    def __init__(self, *, test_prop: builtins.str) -> None:
        '''
        :param test_prop: 
        '''
        if __debug__:
            type_hints = typing.get_type_hints(_typecheckingstub__a410f745cf17b952ca7c740989abf5daa91608073357db663df598089ac271c0)
            check_type(argname="argument test_prop", value=test_prop, expected_type=type_hints["test_prop"])
        self._values: typing.Dict[builtins.str, typing.Any] = {
            "test_prop": test_prop,
        }

    @builtins.property
    def test_prop(self) -> builtins.str:
        result = self._values.get("test_prop")
        assert result is not None, "Required property 'test_prop' is missing"
        return typing.cast(builtins.str, result)

    def __eq__(self, rhs: typing.Any) -> builtins.bool:
        return isinstance(rhs, self.__class__) and rhs._values == self._values

    def __ne__(self, rhs: typing.Any) -> builtins.bool:
        return not (rhs == self)

    def __repr__(self) -> str:
        return "BProps(%s)" % ", ".join(
            k + "=" + repr(v) for k, v in self._values.items()
        )


class TestConstruct(
    _constructs_77d1e7e8.Construct,
    metaclass=jsii.JSIIMeta,
    jsii_type="<construct_library>.testConstruct.TestConstruct",
):
    def __init__(
        self,
        scope: _constructs_77d1e7e8.Construct,
        id: builtins.str,
        *,
        test_prop: builtins.str,
    ) -> None:
        '''
        :param scope: -
        :param id: -
        :param test_prop: 
        '''
        if __debug__:
            type_hints = typing.get_type_hints(_typecheckingstub__6b4fdcd0c8c3aad835d0884ffd49ae763492010eba1e1acc2290e07ff8485088)
            check_type(argname="argument scope", value=scope, expected_type=type_hints["scope"])
            check_type(argname="argument id", value=id, expected_type=type_hints["id"])
        props = AProps(test_prop=test_prop)

        jsii.create(self.__class__, self, [scope, id, props])


__all__ = [
    "AProps",
    "BProps",
    "TestConstruct",
]

publication.publish()

def _typecheckingstub__6c50750a3eed61a5e3bf6110860acdf626b4cbe1cbe7b2cee25f235971070407(
    *,
    test_prop: builtins.str,
) -> None:
    """Type checking stubs"""
    pass

def _typecheckingstub__a410f745cf17b952ca7c740989abf5daa91608073357db663df598089ac271c0(
    *,
    test_prop: builtins.str,
) -> None:
    """Type checking stubs"""
    pass

def _typecheckingstub__6b4fdcd0c8c3aad835d0884ffd49ae763492010eba1e1acc2290e07ff8485088(
    scope: _constructs_77d1e7e8.Construct,
    id: builtins.str,
    *,
    test_prop: builtins.str,
) -> None:
    """Type checking stubs"""
    pass

@sumupitchayan
Copy link

@automartin5000 I think something might be wrong with your Python app code here - why are you defining the TestConstruct class in the Python class, when you already declared the class in constructs.ts above? I'm trying to reproduce this error once again

@automartin5000
Copy link
Author

@automartin5000 I think something might be wrong with your Python app code here - why are you defining the TestConstruct class in the Python class, when you already declared the class in constructs.ts above? I'm trying to reproduce this error once again

I'm not defining it in the Python code. The Python code is the generated code from JSII

@sumupitchayan
Copy link

Python app code:

from <construct_library>.test_construct import AProps
from aws_cdk import App

class TestConstruct:
    def __init__(self, scope, id, props: AProps):
        super().__init__(scope, id, props)

test = TestConstruct(App(), 'TestConstruct', AProps(test_prop='test'))

Error:

_init__.py", line 20, in <module>
    jsii_struct_bases=[BProps],
                       ^^^^^^
NameError: name 'BProps' is not defined

@automartin5000 this is the code I was referring to. Can you show me the CDK app code you are writing? Also, when/where are you seeing this error?

@automartin5000
Copy link
Author

Python app code:

from <construct_library>.test_construct import AProps
from aws_cdk import App

class TestConstruct:
    def __init__(self, scope, id, props: AProps):
        super().__init__(scope, id, props)

test = TestConstruct(App(), 'TestConstruct', AProps(test_prop='test'))

Error:

_init__.py", line 20, in <module>
    jsii_struct_bases=[BProps],
                       ^^^^^^
NameError: name 'BProps' is not defined

@automartin5000 this is the code I was referring to. Can you show me the CDK app code you are writing? Also, when/where are you seeing this error?

That is the Python code being generated by JSII. If you continue to scroll above that, you'll see the code that I wrote. The error is happening at synth.

@sumupitchayan
Copy link

Full JSII generated code (anonymized):

import abc
import builtins
import datetime
import enum
import typing

import jsii
import publication
import typing_extensions

from typeguard import check_type

from .._jsii import *

import constructs as _constructs_77d1e7e8


@jsii.data_type(
    jsii_type="<construct_library>.testConstruct.AProps",
    jsii_struct_bases=[BProps],
    name_mapping={"test_prop": "testProp"},
)
class AProps(BProps):
    def __init__(self, *, test_prop: builtins.str) -> None:
        '''
        :param test_prop: 
        '''
        if __debug__:
            type_hints = typing.get_type_hints(_typecheckingstub__6c50750a3eed61a5e3bf6110860acdf626b4cbe1cbe7b2cee25f235971070407)
            check_type(argname="argument test_prop", value=test_prop, expected_type=type_hints["test_prop"])
        self._values: typing.Dict[builtins.str, typing.Any] = {
            "test_prop": test_prop,
        }

    @builtins.property
    def test_prop(self) -> builtins.str:
        result = self._values.get("test_prop")
        assert result is not None, "Required property 'test_prop' is missing"
        return typing.cast(builtins.str, result)

    def __eq__(self, rhs: typing.Any) -> builtins.bool:
        return isinstance(rhs, self.__class__) and rhs._values == self._values

    def __ne__(self, rhs: typing.Any) -> builtins.bool:
        return not (rhs == self)

    def __repr__(self) -> str:
        return "AProps(%s)" % ", ".join(
            k + "=" + repr(v) for k, v in self._values.items()
        )


@jsii.data_type(
    jsii_type="<construct_library>.testConstruct.BProps",
    jsii_struct_bases=[],
    name_mapping={"test_prop": "testProp"},
)
class BProps:
    def __init__(self, *, test_prop: builtins.str) -> None:
        '''
        :param test_prop: 
        '''
        if __debug__:
            type_hints = typing.get_type_hints(_typecheckingstub__a410f745cf17b952ca7c740989abf5daa91608073357db663df598089ac271c0)
            check_type(argname="argument test_prop", value=test_prop, expected_type=type_hints["test_prop"])
        self._values: typing.Dict[builtins.str, typing.Any] = {
            "test_prop": test_prop,
        }

    @builtins.property
    def test_prop(self) -> builtins.str:
        result = self._values.get("test_prop")
        assert result is not None, "Required property 'test_prop' is missing"
        return typing.cast(builtins.str, result)

    def __eq__(self, rhs: typing.Any) -> builtins.bool:
        return isinstance(rhs, self.__class__) and rhs._values == self._values

    def __ne__(self, rhs: typing.Any) -> builtins.bool:
        return not (rhs == self)

    def __repr__(self) -> str:
        return "BProps(%s)" % ", ".join(
            k + "=" + repr(v) for k, v in self._values.items()
        )


class TestConstruct(
    _constructs_77d1e7e8.Construct,
    metaclass=jsii.JSIIMeta,
    jsii_type="<construct_library>.testConstruct.TestConstruct",
):
    def __init__(
        self,
        scope: _constructs_77d1e7e8.Construct,
        id: builtins.str,
        *,
        test_prop: builtins.str,
    ) -> None:
        '''
        :param scope: -
        :param id: -
        :param test_prop: 
        '''
        if __debug__:
            type_hints = typing.get_type_hints(_typecheckingstub__6b4fdcd0c8c3aad835d0884ffd49ae763492010eba1e1acc2290e07ff8485088)
            check_type(argname="argument scope", value=scope, expected_type=type_hints["scope"])
            check_type(argname="argument id", value=id, expected_type=type_hints["id"])
        props = AProps(test_prop=test_prop)

        jsii.create(self.__class__, self, [scope, id, props])


__all__ = [
    "AProps",
    "BProps",
    "TestConstruct",
]

publication.publish()

def _typecheckingstub__6c50750a3eed61a5e3bf6110860acdf626b4cbe1cbe7b2cee25f235971070407(
    *,
    test_prop: builtins.str,
) -> None:
    """Type checking stubs"""
    pass

def _typecheckingstub__a410f745cf17b952ca7c740989abf5daa91608073357db663df598089ac271c0(
    *,
    test_prop: builtins.str,
) -> None:
    """Type checking stubs"""
    pass

def _typecheckingstub__6b4fdcd0c8c3aad835d0884ffd49ae763492010eba1e1acc2290e07ff8485088(
    scope: _constructs_77d1e7e8.Construct,
    id: builtins.str,
    *,
    test_prop: builtins.str,
) -> None:
    """Type checking stubs"""
    pass

@automartin5000 isn't this the Python code generated by JSII^^? I think your Python app code might be incorrect

@automartin5000
Copy link
Author

I think your Python app code might be incorrect

What's incorrect about it? I'm importing a class generated by JSII created out of the first two Typescript files

@automartin5000
Copy link
Author

@sumupitchayan Just following up to see if you still were unclear on how to replicate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. p1
Projects
None yet
Development

No branches or pull requests

4 participants