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

feat: Add python support to cdk init #2130

Merged
merged 11 commits into from
Apr 2, 2019
6 changes: 6 additions & 0 deletions packages/aws-cdk/lib/init-templates/app/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.swp
cdk.context.json
garnaat marked this conversation as resolved.
Show resolved Hide resolved
package-lock.json
__pycache__
.pytest_cache
.env
42 changes: 42 additions & 0 deletions packages/aws-cdk/lib/init-templates/app/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

Welcome to your CDK Python project!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template should actually be called "sample-app". The "app" template should result in an empty stack (so you don't have to go delete things). See typescript as an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this comment. I was following the existing examples for csharp, java, etc. Is this inconsistent with them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well.... those should be changed to ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Yeah, its kind of backwards now. Do you want to fix all of them now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do you want to change all of those now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's start by renaming this specific template to sample-app and then follow up with a PR for an empty app in python.

garnaat marked this conversation as resolved.
Show resolved Hide resolved

You should explore the contents of this template. It demonstrates a CDK app with two instances of
a stack (`HelloStack`) which also uses a user-defined construct (`HelloConstruct`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention python version used.


The `cdk.json` file tells the CDK Toolkit how to execute your app. It uses a script called `app.sh`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we don't have an app.sh script here (which is a good thing).

to do that.

This project is set up like a standard Python project. The initialization process also creates
a virtualenv within this project, stored under the .env directory.

After the init process completes, you can use the following steps to get your project set up.

```
$ source .env/bin/activate
$ pip install -r requirements.txt
$ python setup.py develop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't develop for libraries (as opposed to apps)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

develop is just a variant of install that allows you to edit your code and run it without having to re-install. This example really consists of both an app and a library. I have restructured the code slightly to make that more clear, I hope.

```

At this point you can now synthesize the CloudFormation template for this code.

```
$ cdk synthesize
garnaat marked this conversation as resolved.
Show resolved Hide resolved
```

You can now begin exploring the source code, contained in the hello directory.
There is also a very trivial test included that can be run like this:

```
$ pytest
```

garnaat marked this conversation as resolved.
Show resolved Hide resolved
# Useful commands

* `cdk ls` list all stacks in the app
* `cdk synth` emits the synthesized CloudFormation template
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk docs` open CDK documentation

Enjoy!
3 changes: 3 additions & 0 deletions packages/aws-cdk/lib/init-templates/app/python/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "python3 hello/hello_stack.py"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this include running via the venv? Isn't that a better experience than requiring users to have pre-actived their shell and THEN running cdk synth?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a good idea. We should be able to just point to the python3 executable in the virtualenv.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EXE on Windows (for 3.7 at least as I just installed it last week) is just python.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets ugly. If we are using a virtualenv that has been created using Python3 then there is no real need to say python3 here. However, that does mean that during the init process we have done a python3 -m venv .env so this, too, will fail on Windows.

We could try python3 for the virtualenv and then try python if that fails but this would cause problems on a machine that does not have Python3 installed at all because we would end up with a virtualenv for Python2.

Maybe this is an argument against trying to create the virtualenv during the init process.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to actually have the app entrypoint an executable file that takes care of all these details, so eventually cdk.json would be:

{
  "app": "bin/my-awesome-app"
}

}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python3

from aws_cdk import (
aws_iam as iam,
aws_s3 as s3,
cdk,
)


class HelloConstruct(cdk.Construct):

@property
def buckets(self):
return tuple(self._buckets)

def __init__(self, scope: cdk.Construct, id: str, num_buckets: int) -> None:
super().__init__(scope, id)
self._buckets = []
for i in range(0, num_buckets):
self._buckets.append(s3.Bucket(self, f"Bucket-{i}"))

def grant_read(self, principal: iam.IPrincipal):
for b in self.buckets:
b.grant_read(principal, "*")
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3

from aws_cdk import (
aws_iam as iam,
aws_sqs as sqs,
aws_sns as sns,
cdk
)

from hello_construct import HelloConstruct

class MyStack(cdk.Stack):

def __init__(self, app: cdk.App, id: str) -> None:
super().__init__(app, id)

queue = sqs.Queue(
garnaat marked this conversation as resolved.
Show resolved Hide resolved
self,
"MyFirstQueue",
visibility_timeout_sec=300,
)

topic = sns.Topic(
self,
"MyFirstTopic",
display_name="My First Topic"
)

topic.subscribe_queue(queue)

hello = HelloConstruct(self, "foobar", num_buckets=4)
user = iam.User(self, "MyUser")
garnaat marked this conversation as resolved.
Show resolved Hide resolved
hello.grant_read(user)


app = cdk.App()
garnaat marked this conversation as resolved.
Show resolved Hide resolved
MyStack(app, "hello-cdk-1")
MyStack(app, "hello-cdk-2")

app.run()

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
aws-cdk.cdk
aws-cdk.aws_iam
aws-cdk.aws_sqs
aws-cdk.aws_sns
aws-cdk.aws_s3
pytest
50 changes: 50 additions & 0 deletions packages/aws-cdk/lib/init-templates/app/python/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import json
import setuptools


with open("README.md") as fp:
long_description = fp.read()


setuptools.setup(
name="hello",
version="0.0.1",

description="A sample CDK Python app",
long_description=long_description,
long_description_content_type="text/markdown",

author="author",

package_dir={"": "hello"},
packages=setuptools.find_packages(where="hello"),

install_requires=[
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense in python-land to have this duplicated with requirements.txt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many packages use both but I have simplified this to put the requirements in the setup.py and then tell pip to use those rather than list them explicitly in requirements.txt.

"aws-cdk.cdk",
"aws-cdk.aws_iam",
"aws-cdk.aws_sqs",
"aws-cdk.aws_sns",
"aws-cdk.aws_s3",
],

python_requires=">=3.6",

classifiers=[
"Development Status :: 4 - Beta",

"Intended Audience :: Developers",

"License :: OSI Approved :: Apache Software License",

"Programming Language :: JavaScript",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",

"Topic :: Software Development :: Code Generators",
"Topic :: Utilities",

"Typing :: Typed",
],
)
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import unittest

from aws_cdk import cdk

from hello.hello_construct import HelloConstruct

class TestHelloConstruct(unittest.TestCase):

def setUp(self):
self.app = cdk.App()
self.stack = cdk.Stack(self.app, "TestStack")

def test_num_buckets(self):
num_buckets = 10
hello = HelloConstruct(self.stack, "Test1", num_buckets)
assert len(hello.buckets) == num_buckets
16 changes: 16 additions & 0 deletions packages/aws-cdk/lib/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ async function postInstall(language: string, canUseNetwork: boolean) {
return await postInstallTypescript(canUseNetwork);
case 'java':
return await postInstallJava(canUseNetwork);
case 'python':
return await postInstallPython(canUseNetwork);
}
}

Expand Down Expand Up @@ -260,6 +262,20 @@ async function postInstallJava(canUseNetwork: boolean) {
await execute('mvn', 'package');
}

async function postInstallPython(canUseNetwork: boolean) {
if (!canUseNetwork) {
print(`Please run ${colors.green('python -m venv .env')}!`);
return;
}

print(`Executing ${colors.green('python -m venv .env')}`);
try {
await execute('python3', '-m venv', '.env');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to also do the pip install stuff here so people can get started quicker?

} catch (e) {
throw new Error(`${colors.green('python3 -m venv .env')} failed: ` + e.message);
}
}

/**
* @param dir a directory to be checked
* @returns true if ``dir`` is within a git repository.
Expand Down