From c5dae879a54dde913cc2b6c5b0f9e57a310f07b7 Mon Sep 17 00:00:00 2001 From: Burak Date: Mon, 17 Oct 2022 15:52:37 +0100 Subject: [PATCH] Python: Migrate to new service builder (#1846) * Python: Migrate to new service builder * Fix orderings of imports --- .../generators/PythonApplicationGenerator.kt | 38 +++++++++------ .../src/server.rs | 48 ++++++++++++------- 2 files changed, 53 insertions(+), 33 deletions(-) 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 9fd367617..2dd7c76ae 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 @@ -17,9 +17,11 @@ import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.Errors import software.amazon.smithy.rust.codegen.core.smithy.Inputs import software.amazon.smithy.rust.codegen.core.smithy.Outputs +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.inputShape import software.amazon.smithy.rust.codegen.core.util.outputShape +import software.amazon.smithy.rust.codegen.core.util.toPascalCase 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.smithy.ServerCargoDependency @@ -69,6 +71,8 @@ class PythonApplicationGenerator( private val symbolProvider = codegenContext.symbolProvider private val libName = "lib${codegenContext.settings.moduleName.toSnakeCase()}" private val runtimeConfig = codegenContext.runtimeConfig + private val service = codegenContext.serviceShape + private val serviceName = service.id.name.toPascalCase() private val model = codegenContext.model private val codegenScope = arrayOf( @@ -84,6 +88,7 @@ class PythonApplicationGenerator( "hyper" to PythonServerCargoDependency.Hyper.asType(), "HashMap" to RustType.HashMap.RuntimeType, "parking_lot" to PythonServerCargoDependency.ParkingLot.asType(), + "http" to RuntimeType.http, ) fun render(writer: RustWriter) { @@ -175,14 +180,19 @@ class PythonApplicationGenerator( rustBlockTemplate( """ - // Dynamically codegenerate the routes, allowing to build the Smithy [#{SmithyServer}::routing::Router]. - fn build_router(&mut self, event_loop: &#{pyo3}::PyAny) -> #{pyo3}::PyResult<#{SmithyServer}::routing::Router> + fn build_service(&mut self, event_loop: &#{pyo3}::PyAny) -> #{pyo3}::PyResult< + #{tower}::util::BoxCloneService< + #{http}::Request<#{SmithyServer}::body::Body>, + #{http}::Response<#{SmithyServer}::body::BoxBody>, + std::convert::Infallible + > + > """, *codegenScope, ) { rustTemplate( """ - let router = crate::operation_registry::OperationRegistryBuilder::default(); + let builder = crate::service::$serviceName::builder(); """, *codegenScope, ) @@ -193,8 +203,8 @@ class PythonApplicationGenerator( """ let ${name}_locals = #{pyo3_asyncio}::TaskLocals::new(event_loop); let handler = self.handlers.get("$name").expect("Python handler for operation `$name` not found").clone(); - let router = router.$name(move |input, state| { - #{pyo3_asyncio}::tokio::scope(${name}_locals, crate::operation_handler::$name(input, state, handler)) + let builder = builder.$name(move |input, state| { + #{pyo3_asyncio}::tokio::scope(${name}_locals.clone(), crate::operation_handler::$name(input, state, handler.clone())) }); """, *codegenScope, @@ -202,16 +212,14 @@ class PythonApplicationGenerator( } rustTemplate( """ - let middleware_locals = pyo3_asyncio::TaskLocals::new(event_loop); + let middleware_locals = #{pyo3_asyncio}::TaskLocals::new(event_loop); let service = #{tower}::ServiceBuilder::new() + .boxed_clone() .layer( #{SmithyPython}::PyMiddlewareLayer::<#{Protocol}>::new(self.middlewares.clone(), middleware_locals), - ); - let router: #{SmithyServer}::routing::Router = router - .build() - .expect("Unable to build operation registry") - .into(); - Ok(router.layer(service)) + ) + .service(builder.build()); + Ok(service) """, "Protocol" to protocol.markerStruct(), *codegenScope, @@ -268,7 +276,7 @@ class PythonApplicationGenerator( use #{SmithyPython}::PyApp; self.run_lambda_handler(py) } - /// Build the router and start a single worker. + /// Build the service and start a single worker. ##[pyo3(text_signature = "(${'$'}self, socket, worker_number)")] pub fn start_worker( &mut self, @@ -278,8 +286,8 @@ class PythonApplicationGenerator( ) -> pyo3::PyResult<()> { use #{SmithyPython}::PyApp; let event_loop = self.configure_python_event_loop(py)?; - let router = self.build_and_configure_router(py, event_loop)?; - self.start_hyper_worker(py, socket, event_loop, router, worker_number) + let service = self.build_and_configure_service(py, event_loop)?; + self.start_hyper_worker(py, socket, event_loop, service, worker_number) } """, *codegenScope, diff --git a/rust-runtime/aws-smithy-http-server-python/src/server.rs b/rust-runtime/aws-smithy-http-server-python/src/server.rs index d9d68ff2c..e2749ce51 100644 --- a/rust-runtime/aws-smithy-http-server-python/src/server.rs +++ b/rust-runtime/aws-smithy-http-server-python/src/server.rs @@ -4,17 +4,19 @@ */ // Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. -use std::{collections::HashMap, ops::Deref, process, thread}; +use std::{collections::HashMap, convert::Infallible, ops::Deref, process, thread}; use aws_smithy_http_server::{ - routing::{LambdaHandler, Router}, + body::{Body, BoxBody}, + routing::{IntoMakeService, LambdaHandler}, AddExtensionLayer, }; +use http::{Request, Response}; use parking_lot::Mutex; use pyo3::{prelude::*, types::IntoPyDict}; use signal_hook::{consts::*, iterator::Signals}; use tokio::runtime; -use tower::ServiceBuilder; +use tower::{util::BoxCloneService, ServiceBuilder}; use crate::{middleware::PyMiddlewareHandler, PyMiddlewareType, PyMiddlewares, PySocket}; @@ -39,6 +41,9 @@ impl Deref for PyHandler { } } +// A `BoxCloneService` with default `Request`, `Response` and `Error`. +type Service = BoxCloneService, Response, Infallible>; + /// Trait defining a Python application. /// /// A Python application requires handling of multiple processes, signals and allows to register Python @@ -66,8 +71,8 @@ pub trait PyApp: Clone + pyo3::IntoPy { fn middlewares(&mut self) -> &mut PyMiddlewares; - /// Build the app's `Router` using given `event_loop`. - fn build_router(&mut self, event_loop: &pyo3::PyAny) -> pyo3::PyResult; + /// Build the app's `Service` using given `event_loop`. + fn build_service(&mut self, event_loop: &pyo3::PyAny) -> pyo3::PyResult; /// Handle the graceful termination of Python workers by looping through all the /// active workers and calling `terminate()` on them. If termination fails, this @@ -215,7 +220,7 @@ event_loop.add_signal_handler(signal.SIGINT, py: Python, socket: &PyCell, event_loop: &PyAny, - router: Router, + service: Service, worker_number: isize, ) -> PyResult<()> { // Clone the socket. @@ -241,7 +246,7 @@ event_loop.add_signal_handler(signal.SIGINT, .expect("Unable to convert socket2::Socket into std::net::TcpListener"), ) .expect("Unable to create hyper server from shared socket") - .serve(router.into_make_service()); + .serve(IntoMakeService::new(service)); tracing::debug!("Started hyper server from shared socket"); // Run forever-ish... @@ -363,10 +368,14 @@ event_loop.add_signal_handler(signal.SIGINT, /// `PythonApplicationGenerator.kt` generates the `start_worker` method: /// /// ```no_run + /// use std::convert::Infallible; /// use std::collections::HashMap; /// use pyo3::prelude::*; /// use aws_smithy_http_server_python::{PyApp, PyHandler, PyMiddlewares}; + /// use aws_smithy_http_server::body::{Body, BoxBody}; /// use parking_lot::Mutex; + /// use http::{Request, Response}; + /// use tower::util::BoxCloneService; /// /// #[pyclass] /// #[derive(Debug, Clone)] @@ -377,7 +386,7 @@ event_loop.add_signal_handler(signal.SIGINT, /// fn context(&self) -> &Option { todo!() } /// fn handlers(&mut self) -> &mut HashMap { todo!() } /// fn middlewares(&mut self) -> &mut PyMiddlewares { todo!() } - /// fn build_router(&mut self, event_loop: &PyAny) -> PyResult { todo!() } + /// fn build_service(&mut self, event_loop: &PyAny) -> PyResult, Response, Infallible>> { todo!() } /// } /// /// #[pymethods] @@ -390,8 +399,8 @@ event_loop.add_signal_handler(signal.SIGINT, /// worker_number: isize, /// ) -> pyo3::PyResult<()> { /// let event_loop = self.configure_python_event_loop(py)?; - /// let router = self.build_router(event_loop)?; - /// self.start_hyper_worker(py, socket, event_loop, router, worker_number) + /// let service = self.build_service(event_loop)?; + /// self.start_hyper_worker(py, socket, event_loop, service, worker_number) /// } /// } /// ``` @@ -459,13 +468,13 @@ event_loop.add_signal_handler(signal.SIGINT, /// it starts the Lambda handler on the current process. fn run_lambda_handler(&mut self, py: Python) -> PyResult<()> { let event_loop = self.configure_python_event_loop(py)?; - let app = self.build_and_configure_router(py, event_loop)?; + let service = self.build_and_configure_service(py, event_loop)?; let rt = runtime::Builder::new_multi_thread() .enable_all() .build() .expect("unable to start a new tokio runtime for this process"); rt.block_on(async move { - let handler = LambdaHandler::new(app); + let handler = LambdaHandler::new(service); let lambda = lambda_http::run(handler); tracing::debug!("starting lambda handler"); if let Err(err) = lambda.await { @@ -475,17 +484,20 @@ event_loop.add_signal_handler(signal.SIGINT, Ok(()) } - // Builds the router and adds necessary layers to it. - fn build_and_configure_router( + // Builds the `Service` and adds necessary layers to it. + fn build_and_configure_service( &mut self, py: Python, event_loop: &pyo3::PyAny, - ) -> pyo3::PyResult { - let app = self.build_router(event_loop)?; + ) -> pyo3::PyResult { + let service = self.build_service(event_loop)?; // Create the `PyState` object from the Python context object. let context = self.context().clone().unwrap_or_else(|| py.None()); tracing::debug!("add middlewares to rust python router"); - let app = app.layer(ServiceBuilder::new().layer(AddExtensionLayer::new(context))); - Ok(app) + let service = ServiceBuilder::new() + .boxed_clone() + .layer(AddExtensionLayer::new(context)) + .service(service); + Ok(service) } } -- GitLab