I wrote a lot of Pulumi code in TypeScript and got...
# python
c
I wrote a lot of Pulumi code in TypeScript and got used to its extensive type definition, which is very convenient when working with VSCode. I recently started a Python-based Pulumi project. I am surprised that it looks like typing support is inferior in Python. Am I wrong? Am I missing something?
e
We do have typing annotations, but we don't run a type checker by default (unlike for TypeScript). You can use mypy or similar to type check your pulumi python programs, we've discussed internally about how we could make this better. Maybe running mypy automatically by default, or adding an option to Pulumi.yaml to run it?
c
I use mypy. But the way types are defined doesn't feel as natural as I am used to with TypeScript. For example, I am trying to do a simple thing: an
iam.Role
subclass with default tags. Since `___init___`is overloaded with multiple definitions, it's not clear what is the clean way to achieve this while keeping typing benefits in my subclass? For reference, I see:
Copy code
def __init__(__self__,
                 resource_name: str,
                 opts: Optional[pulumi.ResourceOptions] = None,
                 assume_role_policy: Optional[pulumi.Input[str]] = None,
                 description: Optional[pulumi.Input[str]] = None,
                 force_detach_policies: Optional[pulumi.Input[bool]] = None,
                 inline_policies: Optional[pulumi.Input[Sequence[pulumi.Input[pulumi.InputType['RoleInlinePolicyArgs']]]]] = None,
                 managed_policy_arns: Optional[pulumi.Input[Sequence[pulumi.Input[str]]]] = None,
                 max_session_duration: Optional[pulumi.Input[int]] = None,
                 name: Optional[pulumi.Input[str]] = None,
                 name_prefix: Optional[pulumi.Input[str]] = None,
                 path: Optional[pulumi.Input[str]] = None,
                 permissions_boundary: Optional[pulumi.Input[str]] = None,
                 tags: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None,
                 __props__=None):
    def __init__(__self__,
                 resource_name: str,
                 args: RoleArgs,
                 opts: Optional[pulumi.ResourceOptions] = None):
    def __init__(__self__, resource_name: str, *args, **kwargs):
Duplicating #1 doesn't seem wise but this API is the most user-friendly. Duplicating #2 looks easier to maintain, but is less Pythonic when using it. #3 is the actual implementation, easy to override, but not typing-friendly.
ideally, I'd like to use
CustomRole
the same way I use the original
Role
(aka keep all options open) - but that looks hard to achieve?
Not pretty IMO šŸ˜•
In TypeScript, we can do nice type composition like
Omit<RoleArgs, 'assumeRolePolicy'>
. In Python,
RoleArgs
is a class, so I don't see any easy way to do that kind of things. And I'd like to avoid writing a ton of boilerplate/redundant code. Any suggestion?
e
#2 is definitely the cleanest. #1 has issues when resources have properties also called "name" or "opts". It is hard trying to balance well typed code with "pythonic" code.
c
Is it possible to keep typing over Input/Output wrapping/unwrapping like in TS?
e
I don't think so. TypeScript has a really good type system which lets us express adding/removing Input recursively to the fields of a structure. Python just has simple generics.
c
That's what I was afraid of šŸ˜ž
Thanks @echoing-dinner-19531
e
dotnet and java are similar to Python in this regard. Like I don't think I can emphasise just how good and advanced the TypeScript type system is. So moving from that to something else does always feel like you've lost something.
c
Haha. You are making me wonder how dumb it is to write my Pulumi code in Python.
e
I mean there are good reasons to write python code, that's why we support it šŸ™‚
c
It's a Python project, so I felt like it's wiser to write everything in Python... šŸ˜…
e
I mean that might be the right call, but if you know the team will also have good TS experience it might be better to have Python appcode, TS infra code.
c
It's a personal project, so I am the team šŸ˜›
I just wanted to avoid context switching due to multiple languages / more complexity due to 2 very different stacks
e
Yeh that's reasonable, there's a million and one tradeoffs with choices like this it's hard to say what's right.
But if you do stick with Python feel free to raise issues / comment here about any rough spots.
c
I'm too old to look for perfection, so I'm looking for simplicity, which is already very hard to achieve šŸ˜›
Thanks for your support šŸ™‚
One more question, which is not Pulumi-specific, but related to Python typing: is it just me or mypy is very slow in your experience too?
e
Yeh it's not fast
Had similar slowness with a non-pulumi code base at my previous job
c
Okay, thanks!
f
pyright can check types (or pylance) and do it much faster than mypy!
c
That's good to know, thanks @fierce-ability-58936 šŸ™‚
g
First word of warning: I'd stay away from pulumi + python ... TypeScript is much better. (I used pulumi with multiple languages) You will soon start missing interfaces from TS. ā€¢ MyPy is extremely slow ā€¢ Types are lacking and sometimes code completion does not work - it is especially awkward when working with outputs ā€¢ Writing classes in python is somewhat clunky - I started with
pydantic
but it turned out pretty bad because again
Outputs
messed it up. So now I'm writing classes with
Attrs
.
c
Haha. Funny to read that as I'm completing my migration away from Python. It has been a pleasure to rewrite it in TypeScript. My tiny experience with Pulumi+Python makes me completely agree with you @great-sunset-355 šŸ™‚
f
I tend to disagree - my experience with Pulumi+Python has been amazing!
pyright -w
in one console, the same thing as a language server, pytest wth watchexec and everything is nice and tidy. Pulumi with Python feels much less clunky than Go, for example. On the other hand, yes, you don't get these features straight from the box, and it involves installing lots of different tools and utilities. I'd personally prefer Go with generics, really looking forward to it!
c
@fierce-ability-58936 I've no doubt that pyright is a good way to ease the pain of mypy's poor perfomance. However, typing coverage in TypeScript is awesome compared to Python. And the way Python works, there things you can't do.
For example, in TS, you can easily build new types based on existing ones:
Copy code
type ServiceRoleArgs = Omit<iam.RoleArgs, "assumeRolePolicy"> & {
  service: string;
};
Maybe you can achieve the same by metaprogramming Python classes? Don't know. But even if it's possible, it's very complicated.
TypeScript is awesome
e.g. type inference works perfectly across value wrapping/unwrapping. For example,
id
is inferred as a string in TS:
Copy code
const bucket = new Bucket(...);
bucket.id.apply(id => ...);
But in Python it is `Any`:
Copy code
bucket = Bucket(...)
bucket.id.apply(lambda id: ...)
e
But in Python it is `Any`:
Oooh we should look into that! Python has enough support for generics that I'd expect
id
to be correct there.
c
Now you make me doubt. I wrote that based on memories. šŸ˜… Do you want me to reproduce it?
e
If you can do raise a bug about it
c
OK, my bad,
bucket.id.apply(lambda id: id.split())
works. I must've been doing something more complex. I'll play a bit with it and raise an issue if I find something weird.
@echoing-dinner-19531 I played with type inference a bit and I did not find any issue. Sorry for my misleading statement!
Only issue I actually had was slow feedback from mypy. šŸ˜‰
e
No worries šŸ™‚