From 8d2431e66611917ed5a503966fff7ca4f7871476 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Wed, 27 Jul 2022 18:42:58 -0400 Subject: [PATCH 1/8] Expose RouteMap as a python stub --- extensions/rust/src/route_map.rs | 3 ++- starlite/app.py | 5 ++--- starlite/route_map_rs.pyi | 36 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 starlite/route_map_rs.pyi diff --git a/extensions/rust/src/route_map.rs b/extensions/rust/src/route_map.rs index 8a5768c1b8..cabddc284d 100644 --- a/extensions/rust/src/route_map.rs +++ b/extensions/rust/src/route_map.rs @@ -103,6 +103,7 @@ pub struct RouteMap { impl RouteMap { /// Creates an empty `RouteMap` #[new] + #[args(debug = false)] pub fn new(py: Python, debug: bool) -> PyResult { macro_rules! get_attr_and_downcast { ($module:ident, $attr:expr, $downcast_ty:ty) => {{ @@ -180,7 +181,7 @@ impl RouteMap { Ok(()) } - // Given a scope, retrieves the correct ASGI App for the route + /// Given a scope, retrieves the correct ASGI App for the route pub fn resolve_asgi_app(&self, scope: &PyAny) -> PyResult> { let (asgi_handlers, is_asgi) = self.parse_scope_to_route(scope)?; diff --git a/starlite/app.py b/starlite/app.py index 0a41472d0d..86f76ab84f 100644 --- a/starlite/app.py +++ b/starlite/app.py @@ -24,7 +24,7 @@ from starlite.plugins.base import PluginProtocol from starlite.provide import Provide from starlite.response import Response -from starlite.route_map_rs import RouteMap as RouteMapInit +from starlite.route_map_rs import RouteMap from starlite.router import Router from starlite.routes import ASGIRoute, BaseRoute, HTTPRoute, WebSocketRoute from starlite.signature import SignatureModelFactory @@ -47,7 +47,6 @@ from starlette.types import ASGIApp, Receive, Scope, Send from starlite.handlers.base import BaseRouteHandler - from starlite.route_map import RouteMap DEFAULT_OPENAPI_CONFIG = OpenAPIConfig(title="Starlite API", version="1.0.0") DEFAULT_CACHE_CONFIG = CacheConfig() @@ -105,7 +104,7 @@ def __init__( self.plain_routes: Set[str] = set() self.plugins = plugins or [] self.routes: List[BaseRoute] = [] - self.route_map: "RouteMap" = RouteMapInit(self.debug) + self.route_map = RouteMap(self.debug) self.state = State() super().__init__( diff --git a/starlite/route_map_rs.pyi b/starlite/route_map_rs.pyi new file mode 100644 index 0000000000..08a648a992 --- /dev/null +++ b/starlite/route_map_rs.pyi @@ -0,0 +1,36 @@ +from typing import Any, Dict, Sequence + +from starlette.types import ASGIApp, Scope + +from starlite.routes import BaseRoute + +class RouteMap: + def __init__(self, debug: bool = False): + """ + Create a new RouteMap + """ + def add_static_path(self, path: str) -> None: + """ + Adds a new static path by path name + """ + def is_static_path(self, path: str) -> bool: + """ + Checks if a given path refers to a static path + """ + def remove_static_path(self, path: str) -> bool: + """ + Removes a path from the static path set + :return: True if the path was removed, False otherwise + """ + def add_routes(self, routes: Sequence[BaseRoute]) -> None: + """ + Add routes to the map + """ + def resolve_asgi_app(self, scope: Scope) -> ASGIApp: + """ + Given a scope, retrieves the correct ASGI App for the route + """ + def traverse_to_dict(self, path: str) -> Dict[str, Any]: + """ + Given a path, traverses the route map to find the corresponding trie node and returns it as a Dict + """ From b9403ad81cdc819809b3fc1e62c3d4eb360c56ef Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Wed, 27 Jul 2022 19:17:17 -0400 Subject: [PATCH 2/8] Remove unused duplicate of rust extension inside starlite dir --- starlite/extensions/rust/Cargo.lock | 256 -------------- starlite/extensions/rust/Cargo.toml | 12 - starlite/extensions/rust/src/lib.rs | 14 - starlite/extensions/rust/src/route_map.rs | 410 ---------------------- starlite/extensions/rust/src/util.rs | 41 --- starlite/route_map.py | 43 --- 6 files changed, 776 deletions(-) delete mode 100644 starlite/extensions/rust/Cargo.lock delete mode 100644 starlite/extensions/rust/Cargo.toml delete mode 100644 starlite/extensions/rust/src/lib.rs delete mode 100644 starlite/extensions/rust/src/route_map.rs delete mode 100644 starlite/extensions/rust/src/util.rs delete mode 100644 starlite/route_map.py diff --git a/starlite/extensions/rust/Cargo.lock b/starlite/extensions/rust/Cargo.lock deleted file mode 100644 index c86b962407..0000000000 --- a/starlite/extensions/rust/Cargo.lock +++ /dev/null @@ -1,256 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "indoc" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "matchit" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfc802da7b1cf80aefffa0c7b2f77247c8b32206cc83c270b61264f5b360a80" - -[[package]] -name = "once_cell" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "proc-macro2" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pyo3" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "parking_lot", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" -dependencies = [ - "once_cell", - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "rust_backend" -version = "0.1.0" -dependencies = [ - "matchit", - "pyo3", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "smallvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" - -[[package]] -name = "syn" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "target-lexicon" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" - -[[package]] -name = "unicode-ident" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" - -[[package]] -name = "unindent" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/starlite/extensions/rust/Cargo.toml b/starlite/extensions/rust/Cargo.toml deleted file mode 100644 index 081a56d156..0000000000 --- a/starlite/extensions/rust/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "rust_backend" -version = "0.1.0" -edition = "2021" - -[lib] -name = "rust_backend" -crate-type = ["cdylib"] - -[dependencies] -pyo3 = { version = "0.16.5", features = ["extension-module"] } -matchit = "0.6.0" diff --git a/starlite/extensions/rust/src/lib.rs b/starlite/extensions/rust/src/lib.rs deleted file mode 100644 index dd2b2492cf..0000000000 --- a/starlite/extensions/rust/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! A route mapping data structure for use in Starlite - -mod route_map; -mod util; - -use crate::route_map::RouteMap; - -use pyo3::prelude::*; - -#[pymodule] -fn rust_backend(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_class::()?; - Ok(()) -} diff --git a/starlite/extensions/rust/src/route_map.rs b/starlite/extensions/rust/src/route_map.rs deleted file mode 100644 index 3985502260..0000000000 --- a/starlite/extensions/rust/src/route_map.rs +++ /dev/null @@ -1,410 +0,0 @@ -use crate::util::{get_base_components, path_parameters_eq}; - -use std::collections::{hash_map, HashMap, HashSet}; - -use pyo3::{ - prelude::*, - types::{PyDict, PyTuple, PyType}, -}; - -pyo3::import_exception!(starlite, ImproperlyConfiguredException); -pyo3::import_exception!(starlite, NotFoundException); - -/// A context object that stores instance and type data that is needed globally in the trie -struct StarliteContext<'py> { - /// The Starlite instance - starlite: &'py PyAny, - /// HTTPRoute - http_route: &'py PyType, - /// WebSocketRoute - web_socket_route: &'py PyType, - /// ASGIRoute - asgi_route: &'py PyType, -} - -/// A node for the trie -#[derive(Debug, Clone)] -struct Node { - components: HashSet, - children: HashMap, - path_parameters: Option>>>, - asgi_handlers: Option>>, - is_asgi: Option, - static_path: Option, -} - -impl Node { - /// Creates a new trie node - pub fn new() -> Self { - Self { - components: HashSet::new(), - children: HashMap::new(), - path_parameters: None, - asgi_handlers: None, - is_asgi: None, - static_path: None, - } - } - - /// Converts the Rust representation to a PyDict representation - pub fn as_pydict(&self) -> PyResult> { - let gil = Python::acquire_gil(); - let dict = PyDict::new(gil.python()); - - dict.set_item("components", self.components.clone())?; - if let Some(ref asgi_handlers) = self.asgi_handlers { - dict.set_item("asgi_handlers", asgi_handlers)?; - } - - if let Some(ref path_parameters) = self.path_parameters { - dict.set_item("path_parameters", path_parameters)?; - } - if let Some(is_asgi) = self.is_asgi { - dict.set_item("is_asgi", is_asgi)?; - } - if let Some(ref static_path) = self.static_path { - dict.set_item("static_path", static_path)?; - } - - Ok(dict.into()) - } -} - -/// A path router based on a prefix tree / trie. -/// -/// Stores a handler references and other metadata for each node, -/// as well as both static paths and plain routes for use with Starlite. -/// -/// Routes can be added using `add_routes`. -/// Given a scope containing a path, can retrieve handlers using `parse_scope_to_route`. -#[pyclass] -pub struct RouteMap { - map: Node, - static_paths: HashSet, - plain_routes: HashSet, -} - -impl Default for RouteMap { - fn default() -> Self { - Self::new() - } -} - -#[pymethods] -impl RouteMap { - /// Creates an empty `RouteMap` - #[new] - pub fn new() -> Self { - RouteMap { - map: Node::new(), - plain_routes: HashSet::new(), - static_paths: HashSet::new(), - } - } - - /// Adds a new static path by path name - pub fn add_static_path(&mut self, path: &str) -> PyResult<()> { - self.static_paths.insert(path.to_string()); - Ok(()) - } - - /// Checks if a given path refers to a static path - pub fn is_static_path(&self, path: &str) -> PyResult { - Ok(self.static_paths.contains(path)) - } - - /// Removes a path from the static path set - pub fn remove_static_path(&mut self, path: &str) -> PyResult { - Ok(self.static_paths.remove(path)) - } - - /// Adds a new plain route by path name - pub fn add_plain_route(&mut self, path: &str) -> PyResult<()> { - self.plain_routes.insert(path.to_string()); - Ok(()) - } - - /// Checks if a given path refers to a plain route - pub fn is_plain_route(&self, path: &str) -> PyResult { - Ok(self.plain_routes.contains(path)) - } - - /// Removes a path from the plain route set - pub fn remove_plain_route(&mut self, path: &str) -> PyResult { - Ok(self.plain_routes.remove(path)) - } - - /// Add routes to the map - pub fn add_routes( - &mut self, - starlite: &PyAny, - http_route: &PyType, - web_socket_route: &PyType, - asgi_route: &PyType, - ) -> PyResult<()> { - let py = starlite.py(); - let ctx = StarliteContext { - starlite, - http_route, - web_socket_route, - asgi_route, - }; - - let routes: Vec<&PyAny> = starlite.getattr("routes")?.extract()?; - for route in routes { - let path: String = route.getattr("path")?.extract()?; - let path_parameters: Vec>> = - route.getattr("path_parameters")?.extract()?; - - let cur = self.add_node_to_route_map(&ctx, route, path, &path_parameters[..])?; - - let cur_path_parameters = cur.path_parameters.as_ref().unwrap(); - - if !path_parameters_eq(cur_path_parameters, &path_parameters, py)? { - return Err(ImproperlyConfiguredException::new_err( - "Should not use routes with conflicting path parameters", - )); - } - } - - Ok(()) - } - - /// Given a scope object, and a reference to Starlite's parser function `parse_path_params`, - /// retrieves the asgi_handlers and is_asgi values from correct trie node. - /// - /// Raises `NotFoundException` if no correlating node is found for the scope's path - pub fn parse_scope_to_route( - &self, - scope: &PyAny, - parse_path_params: &PyAny, - ) -> PyResult<(HashMap>, bool)> { - let mut path = scope - .get_item("path")? - .extract::<&str>()? - .trim() - .to_string(); - - if &path[..] != "/" && path.ends_with('/') { - path = path.strip_suffix('/').unwrap().to_string(); - } - - let cur: &Node; - let path_params: Vec<&str>; - if self.is_plain_route(&path)? { - cur = self.map.children.get(&path).unwrap(); - path_params = vec![]; - } else { - (cur, path_params) = self.traverse_to_node(&path, scope)?; - } - - let args = match cur.path_parameters { - Some(ref path_parameter_defs) => (path_parameter_defs.clone(), path_params), - None => (Vec::>>::new(), path_params), - }; - scope.set_item("path_params", parse_path_params.call1(args)?)?; - - let asgi_handlers = cur.asgi_handlers.clone().unwrap_or_default(); - let is_asgi = cur.is_asgi.unwrap_or(false); - - if cur.asgi_handlers.is_none() && cur.is_asgi.is_none() { - Err(NotFoundException::new_err("")) - } else { - Ok((asgi_handlers, is_asgi)) - } - } - - /// Given a path, traverses the route map to find the corresponding trie node - /// and converts it to a `PyDict` before returning - pub fn traverse_to_dict(&self, path: &str) -> PyResult> { - let mut cur = &self.map; - - if self.is_plain_route(path)? { - cur = cur.children.get(path).unwrap(); - } else { - let components = get_base_components(path); - for component in components { - let components_set = &cur.components; - if components_set.contains(component) { - cur = cur.children.get(component).unwrap(); - continue; - } - if components_set.contains("*") { - cur = cur.children.get("*").unwrap(); - continue; - } - return Err(NotFoundException::new_err("")); - } - } - - cur.as_pydict() - } -} - -impl RouteMap { - /// Set required attributes and route handlers on route_map tree node. - fn configure_route_map_node( - ctx: &StarliteContext, - route: &PyAny, - cur: &mut Node, - path: String, - path_parameters: &[HashMap>], - static_paths: &HashSet, - ) -> PyResult<()> { - let py = route.py(); - let StarliteContext { - starlite, - http_route, - web_socket_route, - asgi_route, - } = ctx; - - if cur.path_parameters.is_none() { - cur.path_parameters = Some(path_parameters.to_vec()); - } - - if cur.asgi_handlers.is_none() { - cur.asgi_handlers = Some(HashMap::new()); - } - - if cur.is_asgi.is_none() { - cur.is_asgi = Some(false); - } - - if static_paths.contains(&path[..]) { - cur.static_path = Some(path); - cur.is_asgi = Some(true); - } - - let asgi_handlers = cur.asgi_handlers.as_mut().unwrap(); - - macro_rules! build_route_middleware_stack { - ($route:ident, $route_handler:ident) => {{ - starlite.call_method( - "build_route_middleware_stack", - ($route, $route_handler), - None, - )? - }}; - } - - macro_rules! generate_single_route_handler_stack { - ($handler_type:expr) => { - let route_handler = route.getattr("route_handler")?; - let middleware_stack = build_route_middleware_stack!(route, route_handler); - asgi_handlers.insert($handler_type.to_string(), middleware_stack.to_object(py)); - }; - } - - if route.is_instance(http_route)? { - let route_handler_map: HashMap = - route.getattr("route_handler_map")?.extract()?; - - for (method, handler_mapping) in route_handler_map.into_iter() { - let handler_mapping = handler_mapping.downcast::()?; - let route_handler = handler_mapping.get_item(0)?; - let middleware_stack = build_route_middleware_stack!(route, route_handler); - asgi_handlers.insert(method, middleware_stack.to_object(py)); - } - } else if route.is_instance(web_socket_route)? { - generate_single_route_handler_stack!("websocket"); - } else if route.is_instance(asgi_route)? { - generate_single_route_handler_stack!("asgi"); - cur.is_asgi = Some(true); - } - - Ok(()) - } - - /// Adds a new route path (e.g. '/foo/bar/{param:int}') into the route_map tree. - /// - /// Inserts non-parameter paths ('plain routes') off the tree's root node. - /// For paths containing parameters, splits the path on '/' and nests each path - /// segment under the previous segment's node (see prefix tree / trie). - fn add_node_to_route_map( - &mut self, - ctx: &StarliteContext, - route: &PyAny, - mut path: String, - path_parameters: &[HashMap>], - ) -> PyResult<&mut Node> { - let py = route.py(); - - let mut cur_node; - - if !path_parameters.is_empty() || self.is_static_path(&path[..])? { - for param_definition in path_parameters { - let param_definition_full = - param_definition.get("full").unwrap().extract::<&str>(py)?; - path = path.replace(param_definition_full, ""); - } - path = path.replace("{}", "*"); - - cur_node = &mut self.map; - - let components = get_base_components(&path); - - for component in components { - let component_set = &mut cur_node.components; - component_set.insert(component.to_string()); - - if let hash_map::Entry::Vacant(e) = cur_node.children.entry(component.to_string()) { - e.insert(Node::new()); - } - cur_node = cur_node.children.get_mut(component).unwrap(); - } - } else { - if let hash_map::Entry::Vacant(e) = self.map.children.entry(path.clone()) { - e.insert(Node::new()); - } - self.add_plain_route(&path[..])?; - cur_node = self.map.children.get_mut(&path[..]).unwrap(); - } - - Self::configure_route_map_node( - ctx, - route, - cur_node, - path, - path_parameters, - &self.static_paths, - )?; - - Ok(cur_node) - } - - /// Given a path and a scope, traverses the route map to find the corresponding trie node - /// and removes any static path from the scope's stored path - fn traverse_to_node<'s, 'p>( - &'s self, - path: &'p str, - scope: &PyAny, - ) -> PyResult<(&'s Node, Vec<&'p str>)> { - let mut path_params = vec![]; - let mut cur = &self.map; - - let components = get_base_components(path); - for component in components { - let components_set = &cur.components; - if components_set.contains(component) { - cur = cur.children.get(component).unwrap(); - continue; - } - if components_set.contains("*") { - path_params.push(component); - cur = cur.children.get("*").unwrap(); - continue; - } - if let Some(ref static_path) = cur.static_path { - if static_path != "/" { - let scope_path: &str = scope.get_item("path")?.extract()?; - scope.set_item("path", scope_path.replace(static_path, ""))?; - } - continue; - } - return Err(NotFoundException::new_err("")); - } - - Ok((cur, path_params)) - } -} diff --git a/starlite/extensions/rust/src/util.rs b/starlite/extensions/rust/src/util.rs deleted file mode 100644 index b51145e601..0000000000 --- a/starlite/extensions/rust/src/util.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::collections::HashMap; - -use pyo3::{prelude::*, types::IntoPyDict}; - -/// Splits a path on '/' and adds all of the 'components' to a new Vec. -/// '/' itself is the first component by default since a leading slash -/// is required by convention -pub fn get_base_components(path: &str) -> Vec<&str> { - let path_split = path.split('/'); - - let mut components = Vec::<&str>::with_capacity(path_split.size_hint().0 + 1); - components.push("/"); - - path_split - .filter(|component| !component.is_empty()) - .for_each(|component| components.push(component)); - - components -} - -/// Given two HashMap slices representing lists of path parameter definition objects, -/// returns whether they are equal. This is just implementing PartialEq, for this type, -/// but with a normal function because a Python instance is required -pub fn path_parameters_eq( - a: &[HashMap>], - b: &[HashMap>], - py: Python, -) -> PyResult { - let mut eq = true; - if a.len() != b.len() { - eq = false; - } else { - for (a, b) in a.iter().zip(b.iter()) { - if !a.into_py_dict(py).eq(b.into_py_dict(py))? { - eq = false; - break; - } - } - } - Ok(eq) -} diff --git a/starlite/route_map.py b/starlite/route_map.py deleted file mode 100644 index 627f7250b5..0000000000 --- a/starlite/route_map.py +++ /dev/null @@ -1,43 +0,0 @@ -# A proxy for a Rust extension class that implements the interface below -# The Rust implementation is located at ./starlite/extensions/rust -from typing import TYPE_CHECKING, Any, Dict, List - -if TYPE_CHECKING: - from starlette.types import ASGIApp, Scope - - from starlite.routes import BaseRoute - - -class RouteMap: - def __init__(self, starlite: Any): - pass - - def add_routes(self, routes: List["BaseRoute"]) -> None: - """ - Add routes to the map - """ - - def resolve_asgi_app(self, scope: "Scope") -> "ASGIApp": - """ - Given a scope, retrieves the correct ASGI App for the route - """ - - def traverse_to_dict(self, path: str) -> Dict[str, Any]: - """ - Given a path, traverses the route map to find the corresponding trie node and returns it as a Dict - """ - - def add_static_path(self, path: str) -> None: - """ - Adds a new static path by path name - """ - - def is_static_path(self, path: str) -> bool: - """ - Checks if a given path refers to a static path - """ - - def remove_static_path(self, path: str) -> bool: - """ - Removes a path from the static path set - """ From 93069f436ecd5cce521c38bf9e5831ae372185d6 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Wed, 27 Jul 2022 19:31:16 -0400 Subject: [PATCH 3/8] Rename module to just `route_map` It's not clear that we need the module to include `_rs` --- extensions/rust/Cargo.lock | 2 +- extensions/rust/Cargo.toml | 4 ++-- extensions/rust/src/lib.rs | 2 +- poetry_build.py | 2 +- starlite/app.py | 2 +- starlite/{route_map_rs.pyi => route_map.pyi} | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename starlite/{route_map_rs.pyi => route_map.pyi} (100%) diff --git a/extensions/rust/Cargo.lock b/extensions/rust/Cargo.lock index fa805cccce..f14279828d 100644 --- a/extensions/rust/Cargo.lock +++ b/extensions/rust/Cargo.lock @@ -158,7 +158,7 @@ dependencies = [ ] [[package]] -name = "route_map_rs" +name = "route_map" version = "0.1.0" dependencies = [ "pyo3", diff --git a/extensions/rust/Cargo.toml b/extensions/rust/Cargo.toml index fc2fe8355e..705c788317 100644 --- a/extensions/rust/Cargo.toml +++ b/extensions/rust/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "route_map_rs" +name = "route_map" version = "0.1.0" edition = "2021" [lib] -name = "route_map_rs" +name = "route_map" crate-type = ["cdylib"] [dependencies] diff --git a/extensions/rust/src/lib.rs b/extensions/rust/src/lib.rs index 5dfad9e4bc..24dfde71b5 100644 --- a/extensions/rust/src/lib.rs +++ b/extensions/rust/src/lib.rs @@ -10,7 +10,7 @@ use crate::route_map::RouteMap; use pyo3::prelude::*; #[pymodule] -fn route_map_rs(_py: Python, m: &PyModule) -> PyResult<()> { +fn route_map(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; Ok(()) } diff --git a/poetry_build.py b/poetry_build.py index f9327a7c77..1641b5497d 100644 --- a/poetry_build.py +++ b/poetry_build.py @@ -8,7 +8,7 @@ def build(setup_kwargs: Dict[str, Any]) -> None: Add rust_extensions to the setup dict """ setup_kwargs["rust_extensions"] = [ - RustExtension("starlite.route_map_rs", path="extensions/rust/Cargo.toml", binding=Binding.PyO3) + RustExtension("starlite.route_map", path="extensions/rust/Cargo.toml", binding=Binding.PyO3) ] setup_kwargs["zip_safe"] = False diff --git a/starlite/app.py b/starlite/app.py index 86f76ab84f..172a6b37bd 100644 --- a/starlite/app.py +++ b/starlite/app.py @@ -24,7 +24,7 @@ from starlite.plugins.base import PluginProtocol from starlite.provide import Provide from starlite.response import Response -from starlite.route_map_rs import RouteMap +from starlite.route_map import RouteMap from starlite.router import Router from starlite.routes import ASGIRoute, BaseRoute, HTTPRoute, WebSocketRoute from starlite.signature import SignatureModelFactory diff --git a/starlite/route_map_rs.pyi b/starlite/route_map.pyi similarity index 100% rename from starlite/route_map_rs.pyi rename to starlite/route_map.pyi From f629395367343528c475497059722a89eca51d71 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Wed, 27 Jul 2022 19:47:41 -0400 Subject: [PATCH 4/8] Remove need for macro for get_attr_and_downcast --- extensions/rust/src/route_map.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/extensions/rust/src/route_map.rs b/extensions/rust/src/route_map.rs index cabddc284d..a922debba1 100644 --- a/extensions/rust/src/route_map.rs +++ b/extensions/rust/src/route_map.rs @@ -105,27 +105,28 @@ impl RouteMap { #[new] #[args(debug = false)] pub fn new(py: Python, debug: bool) -> PyResult { - macro_rules! get_attr_and_downcast { - ($module:ident, $attr:expr, $downcast_ty:ty) => {{ - $module.getattr($attr)?.downcast::<$downcast_ty>()?.into() - }}; + fn get_attr_and_downcast(module: &PyAny, attr: &str) -> PyResult> + where + for<'py> T: PyTryFrom<'py>, + for<'py> &'py T: Into>, + { + Ok(module.getattr(attr)?.downcast::()?.into()) } let parsers = py.import("starlite.parsers")?; - let parse_path_params = get_attr_and_downcast!(parsers, "parse_path_params", PyFunction); + let parse_path_params = get_attr_and_downcast(parsers, "parse_path_params")?; let routes = py.import("starlite.routes")?; - let http_route = get_attr_and_downcast!(routes, "HTTPRoute", PyType); - let web_socket_route = get_attr_and_downcast!(routes, "WebSocketRoute", PyType); - let asgi_route = get_attr_and_downcast!(routes, "ASGIRoute", PyType); + let http_route = get_attr_and_downcast(routes, "HTTPRoute")?; + let web_socket_route = get_attr_and_downcast(routes, "WebSocketRoute")?; + let asgi_route = get_attr_and_downcast(routes, "ASGIRoute")?; let middleware = py.import("starlite.middleware")?; let exception_handler_middleware = - get_attr_and_downcast!(middleware, "ExceptionHandlerMiddleware", PyType); + get_attr_and_downcast(middleware, "ExceptionHandlerMiddleware")?; let starlette_middleware = py.import("starlette.middleware")?; - let starlette_middleware = - get_attr_and_downcast!(starlette_middleware, "Middleware", PyType); + let starlette_middleware = get_attr_and_downcast(starlette_middleware, "Middleware")?; Ok(RouteMap { map: Node::new(), From d8c6d8f2586c6a66a0a1b045efd56d6a1aeefe52 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Wed, 27 Jul 2022 19:51:01 -0400 Subject: [PATCH 5/8] Replace macro with a lambda --- extensions/rust/src/route_map.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/extensions/rust/src/route_map.rs b/extensions/rust/src/route_map.rs index a922debba1..7835f753db 100644 --- a/extensions/rust/src/route_map.rs +++ b/extensions/rust/src/route_map.rs @@ -292,14 +292,12 @@ impl<'rm> ConfigureNodeView<'rm> { let asgi_handlers = cur_node.asgi_handlers.as_mut().unwrap(); - macro_rules! generate_single_route_handler_stack { - ($handler_type:expr) => { - let route_handler = route.getattr("route_handler")?; - let middleware_stack = - build_route_middleware_stack(py, &ctx, route, route_handler)?; - asgi_handlers.insert($handler_type.to_string(), middleware_stack.to_object(py)); - }; - } + let mut generate_single_route_handler_stack = |handler_type: &str| -> PyResult<()> { + let route_handler = route.getattr("route_handler")?; + let middleware_stack = build_route_middleware_stack(py, &ctx, route, route_handler)?; + asgi_handlers.insert(handler_type.to_string(), middleware_stack.into()); + Ok(()) + }; if route.is_instance(http_route.as_ref(py))? { let route_handler_map: HashMap = @@ -313,9 +311,9 @@ impl<'rm> ConfigureNodeView<'rm> { asgi_handlers.insert(method, middleware_stack.to_object(py)); } } else if route.is_instance(web_socket_route.as_ref(py))? { - generate_single_route_handler_stack!("websocket"); + generate_single_route_handler_stack("websocket")?; } else if route.is_instance(asgi_route.as_ref(py))? { - generate_single_route_handler_stack!("asgi"); + generate_single_route_handler_stack("asgi")?; cur_node.is_asgi = true; } From 59342963c3ce6f53f0e09eadba63f951e1c3025c Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Wed, 27 Jul 2022 23:19:15 -0400 Subject: [PATCH 6/8] Use entry.or_insert_with for getting/inserting nodes Should be slightly more efficent: We only have to look up once, rather than twice --- extensions/rust/src/route_map.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/extensions/rust/src/route_map.rs b/extensions/rust/src/route_map.rs index 7835f753db..4ecbf77f57 100644 --- a/extensions/rust/src/route_map.rs +++ b/extensions/rust/src/route_map.rs @@ -1,6 +1,6 @@ use crate::util::{build_route_middleware_stack, get_base_components, path_parameters_eq}; -use std::collections::{hash_map, HashMap, HashSet}; +use std::collections::{HashMap, HashSet}; use pyo3::{ prelude::*, @@ -354,17 +354,18 @@ impl RouteMap { let component_set = &mut cur_node.components; component_set.insert(component.to_string()); - if let hash_map::Entry::Vacant(e) = cur_node.children.entry(component.to_string()) { - e.insert(Node::new()); - } - cur_node = cur_node.children.get_mut(component).unwrap(); + cur_node = cur_node + .children + .entry(component.to_string()) + .or_insert_with(Node::new); } } else { - if let hash_map::Entry::Vacant(e) = self.map.children.entry(path.clone()) { - e.insert(Node::new()); - } self.add_plain_route(&path[..]); - cur_node = self.map.children.get_mut(&path[..]).unwrap(); + cur_node = self + .map + .children + .entry(path.clone()) + .or_insert_with(Node::new); } ConfigureNodeView { From 155b5684eb6f63c70985d4471125ee378bf84414 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Thu, 28 Jul 2022 08:49:10 -0400 Subject: [PATCH 7/8] Remove unneeded `.into()`/`to_object()` calls --- extensions/rust/src/route_map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/rust/src/route_map.rs b/extensions/rust/src/route_map.rs index 4ecbf77f57..32bbb7cb52 100644 --- a/extensions/rust/src/route_map.rs +++ b/extensions/rust/src/route_map.rs @@ -295,7 +295,7 @@ impl<'rm> ConfigureNodeView<'rm> { let mut generate_single_route_handler_stack = |handler_type: &str| -> PyResult<()> { let route_handler = route.getattr("route_handler")?; let middleware_stack = build_route_middleware_stack(py, &ctx, route, route_handler)?; - asgi_handlers.insert(handler_type.to_string(), middleware_stack.into()); + asgi_handlers.insert(handler_type.to_string(), middleware_stack); Ok(()) }; @@ -308,7 +308,7 @@ impl<'rm> ConfigureNodeView<'rm> { let route_handler = handler_mapping.get_item(0)?; let middleware_stack = build_route_middleware_stack(py, &ctx, route, route_handler)?; - asgi_handlers.insert(method, middleware_stack.to_object(py)); + asgi_handlers.insert(method, middleware_stack); } } else if route.is_instance(web_socket_route.as_ref(py))? { generate_single_route_handler_stack("websocket")?; From 0602dba2c0b3cd81cf7fa169890ce8e309958ba8 Mon Sep 17 00:00:00 2001 From: Zachary Dremann Date: Thu, 28 Jul 2022 08:50:54 -0400 Subject: [PATCH 8/8] Add back a comment to the top of the route_map type stubs Explain what the file is, and where the implementation is located --- starlite/route_map.pyi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/starlite/route_map.pyi b/starlite/route_map.pyi index 08a648a992..922aaa333d 100644 --- a/starlite/route_map.pyi +++ b/starlite/route_map.pyi @@ -1,3 +1,7 @@ +# Python type stubs for the route_map module +# +# The implementation of this module is located at `extensions/rust/` in the root of the repository + from typing import Any, Dict, Sequence from starlette.types import ASGIApp, Scope