Unverified Commit c5dae879 authored by Burak's avatar Burak Committed by GitHub
Browse files

Python: Migrate to new service builder (#1846)

* Python: Migrate to new service builder

* Fix orderings of imports
parent 55d16b11
Loading
Loading
Loading
Loading
+23 −15
Original line number Diff line number Diff line
@@ -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,
+30 −18
Original line number Diff line number Diff line
@@ -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<Request<Body>, Response<BoxBody>, 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<PyObject> {

    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<Router>;
    /// Build the app's `Service` using given `event_loop`.
    fn build_service(&mut self, event_loop: &pyo3::PyAny) -> pyo3::PyResult<Service>;

    /// 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<PySocket>,
        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<PyObject> { todo!() }
    ///         fn handlers(&mut self) -> &mut HashMap<String, PyHandler> { todo!() }
    ///         fn middlewares(&mut self) -> &mut PyMiddlewares { todo!() }
    ///         fn build_router(&mut self, event_loop: &PyAny) -> PyResult<aws_smithy_http_server::routing::Router> { todo!() }
    ///         fn build_service(&mut self, event_loop: &PyAny) -> PyResult<BoxCloneService<Request<Body>, Response<BoxBody>, 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<Router> {
        let app = self.build_router(event_loop)?;
    ) -> pyo3::PyResult<Service> {
        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)
    }
}