-
Notifications
You must be signed in to change notification settings - Fork 196
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: Type-stub generation for SSDKs #2149
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
9e359bd
Initial Python stub generation
unexge 787a0b9
Handle default values correctly
unexge 4436d3e
Only generate `__init__` for classes that have constructor signatures
unexge 5d5298e
Preserve doc comments
unexge ae53061
Make context class generic
unexge 5adc034
Put type hint into a string to fix runtime error
unexge bc17873
Run `mypy` on CI
unexge 8a20a4f
Use `make` to build Python SSDKs while generating diffs
unexge ac02352
Escape Python types in Rust comments
unexge 6b77a10
Only mark class methods with
unexge 9bf1b17
Sort imports to minimize diffs
unexge 022835e
Add type annotations for `PySocket`
unexge c8201ab
Dont extend classes from `object` as every class already implicitly e…
unexge 9afcfbb
Use `vars` instead of `inspect.getmembers` to skip inherited members …
unexge d081d8a
Fix linting issues
unexge f7155fc
Add some tests for stubgen and refactor it
unexge 9b29bd1
Add type annotations to `PyMiddlewareException`
unexge 086370c
Fix tests on Python 3.7
unexge d72fff9
Provide default values for `typing.Optional[T]` types in type-stubs
unexge 0ddba29
Update `is_fn_like` to cover more cases
unexge 7ad167d
Remove `tools/smithy-rs-tool-common/`
unexge 7e9c9cb
Make `DECORATORS` an array instead of a list
unexge cd00b0b
Ignore missing type stub errors for `aiohttp`
unexge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
...on/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package software.amazon.smithy.rust.codegen.server.python.smithy | ||
|
||
import software.amazon.smithy.rust.codegen.core.rustlang.RustType | ||
|
||
/** | ||
* A hierarchy of Python types handled by Smithy codegen. | ||
* | ||
* Mostly copied from [RustType] and modified for Python accordingly. | ||
*/ | ||
sealed class PythonType { | ||
/** | ||
* A Python type that contains [member], another [PythonType]. | ||
* Used to generically operate over shapes that contain other shape. | ||
*/ | ||
sealed interface Container { | ||
val member: PythonType | ||
val namespace: String? | ||
val name: String | ||
} | ||
|
||
/** | ||
* Name refers to the top-level type for import purposes. | ||
*/ | ||
abstract val name: String | ||
|
||
open val namespace: String? = null | ||
|
||
object None : PythonType() { | ||
override val name: String = "None" | ||
} | ||
|
||
object Bool : PythonType() { | ||
override val name: String = "bool" | ||
} | ||
|
||
object Int : PythonType() { | ||
override val name: String = "int" | ||
} | ||
|
||
object Float : PythonType() { | ||
override val name: String = "float" | ||
} | ||
|
||
object Str : PythonType() { | ||
override val name: String = "str" | ||
} | ||
|
||
object Any : PythonType() { | ||
override val name: String = "Any" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class List(override val member: PythonType) : PythonType(), Container { | ||
override val name: String = "List" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Dict(val key: PythonType, override val member: PythonType) : PythonType(), Container { | ||
override val name: String = "Dict" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Set(override val member: PythonType) : PythonType(), Container { | ||
override val name: String = "Set" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Optional(override val member: PythonType) : PythonType(), Container { | ||
override val name: String = "Optional" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Awaitable(override val member: PythonType) : PythonType(), Container { | ||
override val name: String = "Awaitable" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Callable(val args: kotlin.collections.List<PythonType>, val rtype: PythonType) : PythonType() { | ||
override val name: String = "Callable" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Union(val args: kotlin.collections.List<PythonType>) : PythonType() { | ||
override val name: String = "Union" | ||
override val namespace: String = "typing" | ||
} | ||
|
||
data class Opaque(override val name: String, val rustNamespace: String? = null) : PythonType() { | ||
// Since Python doesn't have a something like Rust's `crate::` we are using a custom placeholder here | ||
// and in our stub generation script we will replace placeholder with the real root module name. | ||
private val pythonRootModulePlaceholder = "__root_module_name__" | ||
|
||
override val namespace: String? = rustNamespace?.split("::")?.joinToString(".") { | ||
when (it) { | ||
"crate" -> pythonRootModulePlaceholder | ||
// In Python, we expose submodules from `aws_smithy_http_server_python` | ||
// like `types`, `middleware`, `tls` etc. from `__root_module__name` | ||
"aws_smithy_http_server_python" -> pythonRootModulePlaceholder | ||
else -> it | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Return corresponding [PythonType] for a [RustType]. | ||
*/ | ||
fun RustType.pythonType(): PythonType = | ||
when (this) { | ||
is RustType.Unit -> PythonType.None | ||
is RustType.Bool -> PythonType.Bool | ||
is RustType.Float -> PythonType.Float | ||
is RustType.Integer -> PythonType.Int | ||
is RustType.String -> PythonType.Str | ||
is RustType.Vec -> PythonType.List(this.member.pythonType()) | ||
is RustType.Slice -> PythonType.List(this.member.pythonType()) | ||
is RustType.HashMap -> PythonType.Dict(this.key.pythonType(), this.member.pythonType()) | ||
is RustType.HashSet -> PythonType.Set(this.member.pythonType()) | ||
is RustType.Reference -> this.member.pythonType() | ||
is RustType.Option -> PythonType.Optional(this.member.pythonType()) | ||
is RustType.Box -> this.member.pythonType() | ||
is RustType.Dyn -> this.member.pythonType() | ||
is RustType.Opaque -> PythonType.Opaque(this.name, this.namespace) | ||
// TODO(Constraints): How to handle this? | ||
// Revisit as part of https://github.com/awslabs/smithy-rs/issues/2114 | ||
Comment on lines
+129
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you have to.. I was talking with @david-perez and right now Python only handles constraints during deserialization and hands you off the original structure, without newtypes if the validation succeeded or an error otherwise. |
||
is RustType.MaybeConstrained -> this.member.pythonType() | ||
} | ||
|
||
/** | ||
* Render this type, including references and generic parameters. | ||
* It generates something like `typing.Dict[String, String]`. | ||
*/ | ||
fun PythonType.render(fullyQualified: Boolean = true): String { | ||
val namespace = if (fullyQualified) { | ||
this.namespace?.let { "$it." } ?: "" | ||
} else "" | ||
val base = when (this) { | ||
is PythonType.None -> this.name | ||
is PythonType.Bool -> this.name | ||
is PythonType.Float -> this.name | ||
is PythonType.Int -> this.name | ||
is PythonType.Str -> this.name | ||
is PythonType.Any -> this.name | ||
is PythonType.Opaque -> this.name | ||
is PythonType.List -> "${this.name}[${this.member.render(fullyQualified)}]" | ||
is PythonType.Dict -> "${this.name}[${this.key.render(fullyQualified)}, ${this.member.render(fullyQualified)}]" | ||
is PythonType.Set -> "${this.name}[${this.member.render(fullyQualified)}]" | ||
is PythonType.Awaitable -> "${this.name}[${this.member.render(fullyQualified)}]" | ||
is PythonType.Optional -> "${this.name}[${this.member.render(fullyQualified)}]" | ||
is PythonType.Callable -> { | ||
val args = this.args.joinToString(", ") { it.render(fullyQualified) } | ||
val rtype = this.rtype.render(fullyQualified) | ||
"${this.name}[[$args], $rtype]" | ||
} | ||
is PythonType.Union -> { | ||
val args = this.args.joinToString(", ") { it.render(fullyQualified) } | ||
"${this.name}[$args]" | ||
} | ||
} | ||
return "$namespace$base" | ||
} | ||
|
||
/** | ||
* Renders [PythonType] with proper escaping for Docstrings. | ||
*/ | ||
fun PythonType.renderAsDocstring(): String = | ||
this.render() | ||
.replace("[", "\\[") | ||
.replace("]", "\\]") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this customization in Python namespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so (well, not easily).. This is a foundational module that is hard to move around or inherit from. We could have a
PyRustWriter
that adds this functionality, but the realRustWriter
is so embedded into the whole codebase that it's going to be really hard to just use something that inherits from it everywhere.To me it is reasonable to have it here, same goes for
toml
for example, but I would like to hear the SDK team thoughts..