diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt index fa1f8eeb65..9dd3d0d658 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonServerCodegenVisitor.kt @@ -152,7 +152,7 @@ class PythonServerCodegenVisitor( rustCrate.useShapeWriter(shape) { // Use Python specific structure generator that adds the #[pyclass] attribute // and #[pymethods] implementation. - PythonServerStructureGenerator(model, codegenContext.symbolProvider, this, shape).render() + PythonServerStructureGenerator(model, codegenContext, this, shape).render() shape.getTrait()?.also { errorTrait -> ErrorImplGenerator( @@ -190,7 +190,7 @@ class PythonServerCodegenVisitor( override fun unionShape(shape: UnionShape) { logger.info("[python-server-codegen] Generating an union shape $shape") rustCrate.useShapeWriter(shape) { - PythonServerUnionGenerator(model, codegenContext.symbolProvider, this, shape, renderUnknownVariant = false).render() + PythonServerUnionGenerator(model, codegenContext, this, shape, renderUnknownVariant = false).render() } if (shape.isReachableFromOperationInput() && shape.canReachConstrainedShape( diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonType.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonType.kt index 95af2cd14e..42f878d6fc 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonType.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/PythonType.kt @@ -100,17 +100,14 @@ sealed class PythonType { override val namespace = type.namespace } - 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__" + data class Opaque(override val name: String, val pythonRootModuleName: String, val rustNamespace: String? = null) : PythonType() { override val namespace: String? = rustNamespace?.split("::")?.joinToString(".") { when (it) { - "crate" -> pythonRootModulePlaceholder + "crate" -> pythonRootModuleName // 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 + // like `types`, `middleware`, `tls` etc. from Python root module + "aws_smithy_http_server_python" -> pythonRootModuleName else -> it } } @@ -120,26 +117,29 @@ sealed class PythonType { /** * Return corresponding [PythonType] for a [RustType]. */ -fun RustType.pythonType(): PythonType = +fun RustType.pythonType(pythonRootModuleName: String): 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.Application -> PythonType.Application(this.type.pythonType(), this.args.map { it.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 - is RustType.MaybeConstrained -> this.member.pythonType() + is RustType.Vec -> PythonType.List(this.member.pythonType(pythonRootModuleName)) + is RustType.Slice -> PythonType.List(this.member.pythonType(pythonRootModuleName)) + is RustType.HashMap -> PythonType.Dict(this.key.pythonType(pythonRootModuleName), this.member.pythonType(pythonRootModuleName)) + is RustType.HashSet -> PythonType.Set(this.member.pythonType(pythonRootModuleName)) + is RustType.Reference -> this.member.pythonType(pythonRootModuleName) + is RustType.Option -> PythonType.Optional(this.member.pythonType(pythonRootModuleName)) + is RustType.Box -> this.member.pythonType(pythonRootModuleName) + is RustType.Dyn -> this.member.pythonType(pythonRootModuleName) + is RustType.Application -> PythonType.Application( + this.type.pythonType(pythonRootModuleName), + this.args.map { + it.pythonType(pythonRootModuleName) + }, + ) + is RustType.Opaque -> PythonType.Opaque(this.name, pythonRootModuleName, rustNamespace = this.namespace) + is RustType.MaybeConstrained -> this.member.pythonType(pythonRootModuleName) } /** diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonApplicationGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonApplicationGenerator.kt index deec6f6b29..96ecbfe75c 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonApplicationGenerator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonApplicationGenerator.kt @@ -250,11 +250,11 @@ class PythonApplicationGenerator( """, *codegenScope, ) { - val middlewareRequest = PythonType.Opaque("Request", "crate::middleware") - val middlewareResponse = PythonType.Opaque("Response", "crate::middleware") + val middlewareRequest = PythonType.Opaque("Request", libName, rustNamespace = "crate::middleware") + val middlewareResponse = PythonType.Opaque("Response", libName, rustNamespace = "crate::middleware") val middlewareNext = PythonType.Callable(listOf(middlewareRequest), PythonType.Awaitable(middlewareResponse)) val middlewareFunc = PythonType.Callable(listOf(middlewareRequest, middlewareNext), PythonType.Awaitable(middlewareResponse)) - val tlsConfig = PythonType.Opaque("TlsConfig", "crate::tls") + val tlsConfig = PythonType.Opaque("TlsConfig", libName, rustNamespace = "crate::tls") rustTemplate( """ @@ -344,9 +344,9 @@ class PythonApplicationGenerator( val operationName = symbolProvider.toSymbol(operation).name val fnName = RustReservedWords.escapeIfNeeded(symbolProvider.toSymbol(operation).name.toSnakeCase()) - val input = PythonType.Opaque("${operationName}Input", "crate::input") - val output = PythonType.Opaque("${operationName}Output", "crate::output") - val context = PythonType.Opaque("Ctx") + val input = PythonType.Opaque("${operationName}Input", libName, rustNamespace = "crate::input") + val output = PythonType.Opaque("${operationName}Output", libName, rustNamespace = "crate::output") + val context = PythonType.Opaque("Ctx", libName) val returnType = PythonType.Union(listOf(output, PythonType.Awaitable(output))) val handler = PythonType.Union( listOf( diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt index 1502f5422a..d9d2df8cc2 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt @@ -18,16 +18,17 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustInlineTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable -import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.rustType import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.isEventStream +import software.amazon.smithy.rust.codegen.core.util.toSnakeCase import software.amazon.smithy.rust.codegen.server.python.smithy.PythonEventStreamSymbolProvider import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCargoDependency import software.amazon.smithy.rust.codegen.server.python.smithy.PythonType import software.amazon.smithy.rust.codegen.server.python.smithy.pythonType import software.amazon.smithy.rust.codegen.server.python.smithy.renderAsDocstring +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext /** * To share structures defined in Rust with Python, `pyo3` provides the `PyClass` trait. @@ -36,11 +37,13 @@ import software.amazon.smithy.rust.codegen.server.python.smithy.renderAsDocstrin */ class PythonServerStructureGenerator( model: Model, - private val symbolProvider: RustSymbolProvider, + private val codegenContext: ServerCodegenContext, private val writer: RustWriter, private val shape: StructureShape, -) : StructureGenerator(model, symbolProvider, writer, shape, emptyList()) { +) : StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList()) { + private val symbolProvider = codegenContext.symbolProvider + private val libName = codegenContext.settings.moduleName.toSnakeCase() private val pyO3 = PythonServerCargoDependency.PyO3.toType() override fun renderStructure() { @@ -157,9 +160,9 @@ class PythonServerStructureGenerator( private fun memberPythonType(shape: MemberShape, symbol: Symbol): PythonType = if (shape.isEventStream(model)) { val eventStreamSymbol = PythonEventStreamSymbolProvider.parseSymbol(symbol) - val innerT = eventStreamSymbol.innerT.pythonType() + val innerT = eventStreamSymbol.innerT.pythonType(libName) PythonType.AsyncIterator(innerT) } else { - symbol.rustType().pythonType() + symbol.rustType().pythonType(libName) } } diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerUnionGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerUnionGenerator.kt index 6b7a7bee8a..01a2a833af 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerUnionGenerator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerUnionGenerator.kt @@ -26,6 +26,7 @@ import software.amazon.smithy.rust.codegen.core.util.toSnakeCase import software.amazon.smithy.rust.codegen.server.python.smithy.PythonServerCargoDependency import software.amazon.smithy.rust.codegen.server.python.smithy.pythonType import software.amazon.smithy.rust.codegen.server.python.smithy.renderAsDocstring +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext /* * Generate unions that are compatible with Python by wrapping the Rust implementation into @@ -34,11 +35,13 @@ import software.amazon.smithy.rust.codegen.server.python.smithy.renderAsDocstrin */ class PythonServerUnionGenerator( model: Model, - private val symbolProvider: SymbolProvider, + private val codegenContext: ServerCodegenContext, private val writer: RustWriter, shape: UnionShape, private val renderUnknownVariant: Boolean = true, -) : UnionGenerator(model, symbolProvider, writer, shape, renderUnknownVariant) { +) : UnionGenerator(model, codegenContext.symbolProvider, writer, shape, renderUnknownVariant) { + private val symbolProvider = codegenContext.symbolProvider + private val libName = codegenContext.settings.moduleName.toSnakeCase() private val sortedMembers: List = shape.allMembers.values.sortedBy { symbolProvider.toMemberName(it) } private val unionSymbol = symbolProvider.toSymbol(shape) @@ -125,7 +128,7 @@ class PythonServerUnionGenerator( } } else { val memberSymbol = symbolProvider.toSymbol(member) - val pythonType = memberSymbol.rustType().pythonType() + val pythonType = memberSymbol.rustType().pythonType(libName) val targetType = memberSymbol.rustType() Attribute("staticmethod").render(writer) writer.rust( @@ -166,7 +169,7 @@ class PythonServerUnionGenerator( } } else { val memberSymbol = symbolProvider.toSymbol(member) - val pythonType = memberSymbol.rustType().pythonType() + val pythonType = memberSymbol.rustType().pythonType(libName) val targetSymbol = symbolProvider.toSymbol(model.expectShape(member.target)) val rustType = memberSymbol.rustType() writer.rust( @@ -181,12 +184,13 @@ class PythonServerUnionGenerator( } else { "variant.clone()" } + val errorVariant = memberSymbol.rustType().pythonType(libName).renderAsDocstring() rustTemplate( """ match self.0.as_$funcNamePart() { Ok(variant) => Ok($variantType), Err(_) => Err(#{pyo3}::exceptions::PyValueError::new_err( - r"${unionSymbol.name} variant is not of type ${memberSymbol.rustType().pythonType().renderAsDocstring()}" + r"${unionSymbol.name} variant is not of type $errorVariant" )), } """, diff --git a/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt b/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt index 1473edcffe..c552d32edd 100644 --- a/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt +++ b/codegen-server/python/src/test/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonTypeInformationGenerationTest.kt @@ -28,9 +28,8 @@ internal class PythonTypeInformationGenerationTest { val foo = model.lookup("test#Foo") val codegenContext = serverTestCodegenContext(model) - val symbolProvider = codegenContext.symbolProvider val writer = RustWriter.forModule("model") - PythonServerStructureGenerator(model, symbolProvider, writer, foo).render() + PythonServerStructureGenerator(model, codegenContext, writer, foo).render() val result = writer.toString() diff --git a/examples/python/README.md b/examples/python/README.md index ebe6cfb39e..e3fd43929a 100644 --- a/examples/python/README.md +++ b/examples/python/README.md @@ -12,8 +12,8 @@ The Python implementation of the service can be found inside * [Python stub generation](#python-stub-generation) * [Run](#run) * [Test](#test) -* [Uvloop](#uvloop) * [MacOs](#macos) +* [Running servers on AWS Lambda](#running-servers-on-aws-lambda) ## Build @@ -26,10 +26,14 @@ build. Ensure these dependencies are installed. -``` +```bash pip install maturin uvloop aiohttp mypy ``` +The server can depend on [uvloop](https://pypi.org/project/uvloop/) for a faster +event loop implementation and it will be automatically used instead of the standard +library event loop if it is found in the dependencies' closure. + ### Makefile The build logic is drive by the Makefile: @@ -37,11 +41,13 @@ The build logic is drive by the Makefile: * `make codegen`: run the codegenerator. * `make build`: build the Maturin package in debug mode (includes type stubs generation). -* `make release`: build the Maturin package in release mode (includes type stub +* `make release`: build the Maturin package in release mode (includes type stubs generation). -* `make install`: install the latest release or debug build. +* `make install-wheel`: install the latest release or debug wheel using `pip`. * `make run`: run the example server. * `make test`: run the end-to-end integration test. +* `make clean`: clean the Cargo artefacts. +* `make dist-clean`: clean the Cargo artefacts and generated folders. ### Python stub generation @@ -52,7 +58,7 @@ It will do first compilation of the crate, generate the types and exit. The script takes some command line arguments: -``` +```bash ./stubgen.sh module_name manifest_path output_directory ``` @@ -72,19 +78,6 @@ tests against it. More info can be found in the `tests` folder of `pokemon-service-test` package. -## Uvloop - -The server can depend on [uvloop](https://pypi.org/project/uvloop/) for a -faster event loop implementation. Uvloop can be installed with your favourite -package manager or by using pip: - -```sh -pip instal uvloop -``` - -and it will be automatically used instead of the standard library event loop if -it is found in the dependencies' closure. - ## MacOs To compile and test on MacOs, please follow the official PyO3 guidelines on how @@ -92,3 +85,73 @@ to [configure your linker](https://pyo3.rs/latest/building_and_distribution.html Please note that the `.cargo/config.toml` with linkers override can be local to your project. + +## Running servers on AWS Lambda + +`aws-smithy-http-server-python` supports running your services on [AWS Lambda](https://aws.amazon.com/lambda/). + +You need to use `run_lambda` method instead of `run` method to start +the [custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) +instead of the [Hyper](https://hyper.rs/) HTTP server. + +In your `app.py`: + +```diff +from pokemon_service_server_sdk import App +from pokemon_service_server_sdk.error import ResourceNotFoundException + +# ... + +# Get the number of requests served by this server. +@app.get_server_statistics +def get_server_statistics( + _: GetServerStatisticsInput, context: Context +) -> GetServerStatisticsOutput: + calls_count = context.get_calls_count() + logging.debug("The service handled %d requests", calls_count) + return GetServerStatisticsOutput(calls_count=calls_count) + +# ... + +-app.run() ++app.run_lambda() +``` + +`aws-smithy-http-server-python` comes with a +[custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) +so you should run your service without any provided runtimes. +You can achieve that with a `Dockerfile` similar to this: + +```dockerfile +# You can use any image that has your desired Python version +FROM public.ecr.aws/lambda/python:3.8-x86_64 + +# Copy your application code to `LAMBDA_TASK_ROOT` +COPY app.py ${LAMBDA_TASK_ROOT} + +# When you build your Server SDK for your service, you will get a Python wheel. +# You just need to copy that wheel and install it via `pip` inside your image. +# Note that you need to build your library for Linux, and Python version used to +# build your SDK should match with your image's Python version. +# For cross compiling, you can consult to: +# https://pyo3.rs/latest/building_and_distribution.html#cross-compiling +COPY wheels/ ${LAMBDA_TASK_ROOT}/wheels +RUN pip3 install ${LAMBDA_TASK_ROOT}/wheels/*.whl + +# You can install your application's other dependencies listed in `requirements.txt`. +COPY requirements.txt . +RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" + +# Create a symlink for your application's entrypoint, +# so we can use `/app.py` to refer it +RUN ln -s ${LAMBDA_TASK_ROOT}/app.py /app.py + +# By default `public.ecr.aws/lambda/python` images comes with Python runtime, +# we need to override `ENTRYPOINT` and `CMD` to not call that runtime and +# instead run directly your service and it will start our custom runtime. +ENTRYPOINT [ "/var/lang/bin/python3.8" ] +CMD [ "/app.py" ] +``` + +See [https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-create-from-base](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-create-from-base) +for more details on building your custom image. diff --git a/examples/python/pokemon_service.py b/examples/python/pokemon_service.py index 210d232687..7ee4a7284f 100644 --- a/examples/python/pokemon_service.py +++ b/examples/python/pokemon_service.py @@ -6,47 +6,38 @@ import argparse import logging import random -from threading import Lock from dataclasses import dataclass -from typing import Dict, Any, List, Optional, Callable, Awaitable, AsyncIterator +from threading import Lock +from typing import Any, AsyncIterator, Awaitable, Callable, Dict, List, Optional from pokemon_service_server_sdk import App -from pokemon_service_server_sdk.tls import TlsConfig from pokemon_service_server_sdk.aws_lambda import LambdaContext from pokemon_service_server_sdk.error import ( + InvalidPokeballError, + MasterBallUnsuccessful, ResourceNotFoundException, UnsupportedRegionError, - MasterBallUnsuccessful, - InvalidPokeballError, ) from pokemon_service_server_sdk.input import ( + CapturePokemonInput, + CheckHealthInput, DoNothingInput, GetPokemonSpeciesInput, GetServerStatisticsInput, - CheckHealthInput, StreamPokemonRadioInput, - CapturePokemonInput, ) from pokemon_service_server_sdk.logging import TracingHandler -from pokemon_service_server_sdk.middleware import ( - MiddlewareException, - Response, - Request, -) -from pokemon_service_server_sdk.model import ( - CapturePokemonEvents, - CaptureEvent, - FlavorText, - Language, -) +from pokemon_service_server_sdk.middleware import MiddlewareException, Request, Response +from pokemon_service_server_sdk.model import CaptureEvent, CapturePokemonEvents, FlavorText, Language from pokemon_service_server_sdk.output import ( + CapturePokemonOutput, + CheckHealthOutput, DoNothingOutput, GetPokemonSpeciesOutput, GetServerStatisticsOutput, - CheckHealthOutput, StreamPokemonRadioOutput, - CapturePokemonOutput, ) +from pokemon_service_server_sdk.tls import TlsConfig from pokemon_service_server_sdk.types import ByteStream # Logging can bee setup using standard Python tooling. We provide @@ -164,17 +155,16 @@ def get_random_radio_stream(self) -> str: # Next is either the next middleware in the stack or the handler. Next = Callable[[Request], Awaitable[Response]] + # This middleware checks the `Content-Type` from the request header, # logs some information depending on that and then calls `next`. @app.middleware async def check_content_type_header(request: Request, next: Next) -> Response: content_type = request.headers.get("content-type") if content_type == "application/json": - logging.debug("Found valid `application/json` content type") + logging.debug("found valid `application/json` content type") else: - logging.warning( - f"Invalid content type {content_type}, dumping headers: {request.headers.items()}" - ) + logging.warning("invalid content type %s, dumping headers: %s", content_type, request.headers.items()) return await next(request) @@ -184,7 +174,7 @@ async def check_content_type_header(request: Request, next: Next) -> Response: @app.middleware async def add_x_amzn_answer_header(request: Request, next: Next) -> Response: request.headers["x-amzn-answer"] = "42" - logging.debug("Setting `x-amzn-answer` header to 42") + logging.debug("setting `x-amzn-answer` header to 42") return await next(request) @@ -205,18 +195,15 @@ async def check_x_amzn_answer_header(request: Request, next: Next) -> Response: # DoNothing operation used for raw benchmarking. @app.do_nothing def do_nothing(_: DoNothingInput) -> DoNothingOutput: - # logging.debug("Running the DoNothing operation") return DoNothingOutput() # Get the translation of a Pokémon specie or an error. @app.get_pokemon_species -def get_pokemon_species( - input: GetPokemonSpeciesInput, context: Context -) -> GetPokemonSpeciesOutput: +def get_pokemon_species(input: GetPokemonSpeciesInput, context: Context) -> GetPokemonSpeciesOutput: if context.lambda_ctx is not None: logging.debug( - "Lambda Context: %s", + "lambda Context: %s", dict( request_id=context.lambda_ctx.request_id, deadline=context.lambda_ctx.deadline, @@ -229,24 +216,19 @@ def get_pokemon_species( context.increment_calls_count() flavor_text_entries = context.get_pokemon_description(input.name) if flavor_text_entries: - logging.debug("Total requests executed: %s", context.get_calls_count()) - logging.info("Found description for Pokémon %s", input.name) - logging.error("Found some stuff") - return GetPokemonSpeciesOutput( - name=input.name, flavor_text_entries=flavor_text_entries - ) + logging.debug("total requests executed: %s", context.get_calls_count()) + logging.info("found description for Pokémon %s", input.name) + return GetPokemonSpeciesOutput(name=input.name, flavor_text_entries=flavor_text_entries) else: - logging.warning("Description for Pokémon %s not in the database", input.name) + logging.warning("description for Pokémon %s not in the database", input.name) raise ResourceNotFoundException("Requested Pokémon not available") # Get the number of requests served by this server. @app.get_server_statistics -def get_server_statistics( - _: GetServerStatisticsInput, context: Context -) -> GetServerStatisticsOutput: +def get_server_statistics(_: GetServerStatisticsInput, context: Context) -> GetServerStatisticsOutput: calls_count = context.get_calls_count() - logging.debug("The service handled %d requests", calls_count) + logging.debug("the service handled %d requests", calls_count) return GetServerStatisticsOutput(calls_count=calls_count) @@ -398,7 +380,7 @@ async def events(input: CapturePokemonInput) -> AsyncIterator[CapturePokemonEven break # You can catch modeled errors and act accordingly, they will terminate the event stream except MasterBallUnsuccessful as err: - logging.error(f"masterball unsuccessful: {err}") + logging.error("masterball unsuccessful: %s", err) # Here event stream is going to be completed because we stopped receiving events and we'll # no longer `yield` new values and this asynchronous Python generator will end here @@ -411,17 +393,15 @@ async def events(input: CapturePokemonInput) -> AsyncIterator[CapturePokemonEven # Stream a random Pokémon song. @app.stream_pokemon_radio -async def stream_pokemon_radio( - _: StreamPokemonRadioInput, context: Context -) -> StreamPokemonRadioOutput: +async def stream_pokemon_radio(_: StreamPokemonRadioInput, context: Context) -> StreamPokemonRadioOutput: import aiohttp radio_url = context.get_random_radio_stream() - logging.info("Random radio URL for this stream is %s", radio_url) + logging.info("random radio URL for this stream is %s", radio_url) async with aiohttp.ClientSession() as session: async with session.get(radio_url) as response: data = ByteStream(await response.read()) - logging.debug("Successfully fetched radio url %s", radio_url) + logging.debug("successfully fetched radio url %s", radio_url) return StreamPokemonRadioOutput(data=data) diff --git a/rust-runtime/aws-smithy-http-server-python/README.md b/rust-runtime/aws-smithy-http-server-python/README.md index 7b8d218f3c..da3dc2c131 100644 --- a/rust-runtime/aws-smithy-http-server-python/README.md +++ b/rust-runtime/aws-smithy-http-server-python/README.md @@ -2,76 +2,6 @@ Server libraries for smithy-rs generated servers, targeting pure Python business logic. -## Running servers on AWS Lambda - -`aws-smithy-http-server-python` supports running your services on [AWS Lambda](https://aws.amazon.com/lambda/). - -You need to use `run_lambda` method instead of `run` method to start -the [custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) -instead of the [Hyper](https://hyper.rs/) HTTP server. - -In your `app.py`: - -```diff -from pokemon_service_server_sdk import App -from pokemon_service_server_sdk.error import ResourceNotFoundException - -# ... - -# Get the number of requests served by this server. -@app.get_server_statistics -def get_server_statistics( - _: GetServerStatisticsInput, context: Context -) -> GetServerStatisticsOutput: - calls_count = context.get_calls_count() - logging.debug("The service handled %d requests", calls_count) - return GetServerStatisticsOutput(calls_count=calls_count) - -# ... - --app.run() -+app.run_lambda() -``` - -`aws-smithy-http-server-python` comes with a -[custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) -so you should run your service without any provided runtimes. -You can achieve that with a `Dockerfile` similar to this: - -```dockerfile -# You can use any image that has your desired Python version -FROM public.ecr.aws/lambda/python:3.8-x86_64 - -# Copy your application code to `LAMBDA_TASK_ROOT` -COPY app.py ${LAMBDA_TASK_ROOT} - -# When you build your Server SDK for your service, you will get a Python wheel. -# You just need to copy that wheel and install it via `pip` inside your image. -# Note that you need to build your library for Linux, and Python version used to -# build your SDK should match with your image's Python version. -# For cross compiling, you can consult to: -# https://pyo3.rs/latest/building_and_distribution.html#cross-compiling -COPY wheels/ ${LAMBDA_TASK_ROOT}/wheels -RUN pip3 install ${LAMBDA_TASK_ROOT}/wheels/*.whl - -# You can install your application's other dependencies listed in `requirements.txt`. -COPY requirements.txt . -RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" - -# Create a symlink for your application's entrypoint, -# so we can use `/app.py` to refer it -RUN ln -s ${LAMBDA_TASK_ROOT}/app.py /app.py - -# By default `public.ecr.aws/lambda/python` images comes with Python runtime, -# we need to override `ENTRYPOINT` and `CMD` to not call that runtime and -# instead run directly your service and it will start our custom runtime. -ENTRYPOINT [ "/var/lang/bin/python3.8" ] -CMD [ "/app.py" ] -``` - -See [https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-create-from-base](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-create-from-base) -for more details on building your custom image. - This crate is part of the [AWS SDK for Rust](https://awslabs.github.io/aws-sdk-rust/) and the [smithy-rs](https://github.com/awslabs/smithy-rs) code generator. In most cases, it should not be used directly. diff --git a/rust-runtime/aws-smithy-http-server-python/stubgen.py b/rust-runtime/aws-smithy-http-server-python/stubgen.py index 458ee2b9c5..0edf1d3ef2 100644 --- a/rust-runtime/aws-smithy-http-server-python/stubgen.py +++ b/rust-runtime/aws-smithy-http-server-python/stubgen.py @@ -393,7 +393,7 @@ def walk_module(writer: Writer, mod: Any): def generate(module: str, outdir: str): - path = Path(outdir) / f"{module}.pyi" + path = Path(outdir) / "__init__.pyi" writer = Writer( path, module,