Skip to content

Commit

Permalink
v1.2.3 🐈
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertoPrevato authored Feb 6, 2022
1 parent 8e53014 commit ab6355d
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 87 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.3] - 2022-02-06 :cat2:
* Adds support for [WebSocket](https://www.neoteroi.dev/blacksheep/websocket/)

## [1.2.2] - 2021-12-03 :gift:
* Fixes wrong mime type in OpenAPI Documentation for the `form` binder (#212)
* Adds `OpenAPIEvents` to the class handling the generation of OpenAPI Documentation
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ Refer to the documentation for more details and examples.
functions](https://www.neoteroi.dev/blacksheep/request-handlers/), or [class
methods](https://www.neoteroi.dev/blacksheep/controllers/)
* [Middlewares](https://www.neoteroi.dev/blacksheep/middlewares/)
* [WebSocket](https://www.neoteroi.dev/blacksheep/websocket/)
* [Built-in support for dependency
injection](https://www.neoteroi.dev/blacksheep/dependency-injection/)
* [Support for automatic binding of route and query parameters to request
Expand Down
63 changes: 57 additions & 6 deletions blacksheep/server/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ def __init__(self, class_name: str, overriding_class_name: str) -> None:
)


class NameAliasAlreadyDefinedException(BindingException):
def __init__(self, alias: str, overriding_class_name: str) -> None:
super().__init__(
f"There is already a name alias defined for '{alias}', "
f"the second type is: {overriding_class_name}"
)
self.alias = alias


class TypeAliasAlreadyDefinedException(BindingException):
def __init__(self, alias: Any, overriding_class_name: str) -> None:
super().__init__(
f"There is already a type alias defined for '{alias.__name__}', "
f"the second type is: {overriding_class_name}"
)
self.alias = alias


class BinderNotRegisteredForValueType(BindingException):
def __init__(self, value_type: Type["BoundValue"]) -> None:
super().__init__(
Expand All @@ -69,10 +87,23 @@ def __init__(self, value_type: Type["BoundValue"]) -> None:

class BinderMeta(type):
handlers: Dict[Type[Any], Type["Binder"]] = {}
aliases: Dict[Any, Callable[[Services], "Binder"]] = {}

def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
handle = getattr(cls, "handle", None)
name_alias = getattr(cls, "name_alias", None)
type_alias = getattr(cls, "type_alias", None)

if name_alias:
if name_alias in cls.aliases:
raise NameAliasAlreadyDefinedException(name_alias, name)
cls.aliases[name_alias] = cls.from_alias # type: ignore

if type_alias:
if type_alias in cls.aliases:
raise TypeAliasAlreadyDefinedException(type_alias, name)
cls.aliases[type_alias] = cls.from_alias # type: ignore

if handle:
if handle in cls.handlers:
Expand Down Expand Up @@ -208,14 +239,16 @@ class RequestMethod(BoundValue[str]):
"""


class Binder(metaclass=BinderMeta):
class Binder(metaclass=BinderMeta): # type: ignore
handle: ClassVar[Type[BoundValue]]
name_alias: ClassVar[str] = ""
type_alias: ClassVar[Any] = None
_implicit: bool
default: Any

def __init__(
self,
expected_type: T,
expected_type: Any,
name: str = "",
implicit: bool = False,
required: bool = True,
Expand All @@ -229,6 +262,10 @@ def __init__(
self.converter = converter
self.default = empty

@classmethod
def from_alias(cls, services: Services):
return cls() # type: ignore

@property
def implicit(self) -> bool:
return self._implicit
Expand Down Expand Up @@ -260,7 +297,7 @@ def generic_iterable_annotation_item_type(self, param_type):
return str
return item_type

async def get_parameter(self, request: Request) -> Union[T, BoundValue[T]]:
async def get_parameter(self, request: Request) -> Any:
"""
Gets a parameter to be passed to a request handler.
Expand Down Expand Up @@ -297,7 +334,7 @@ def example(id: str):
return self.handle(value)

@abstractmethod
async def get_value(self, request: Request) -> Optional[T]:
async def get_value(self, request: Request) -> Any:
"""Gets a value from the given request object."""


Expand Down Expand Up @@ -789,18 +826,24 @@ async def get_value(self, request: Request) -> Optional[T]:


class RequestBinder(Binder):
name_alias = "request"
type_alias = Request

def __init__(self, implicit: bool = True):
super().__init__(Request, implicit=implicit)

async def get_value(self, request: Request) -> Optional[T]:
async def get_value(self, request: Request) -> Any:
return request


class WebSocketBinder(Binder):
name_alias = "websocket"
type_alias = WebSocket

def __init__(self, implicit: bool = True):
super().__init__(WebSocket, implicit=implicit)

async def get_value(self, websocket: WebSocket) -> Optional[T]:
async def get_value(self, websocket: WebSocket) -> Optional[WebSocket]:
return websocket


Expand All @@ -820,6 +863,14 @@ async def get_value(self, request: Request) -> Any:
return self.exact_object


class ServicesBinder(ExactBinder):
name_alias = "services"

@classmethod
def from_alias(cls, services: Services) -> "ServicesBinder":
return cls(services)


class ClientInfoBinder(Binder):
handle = ClientInfo

Expand Down
1 change: 1 addition & 0 deletions blacksheep/server/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
trace = router.trace
options = router.options
connect = router.connect
ws = router.ws


class CannotDetermineDefaultViewNameError(RuntimeError):
Expand Down
21 changes: 4 additions & 17 deletions blacksheep/server/normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,17 @@
from blacksheep.normalization import copy_special_attributes
from blacksheep.server import responses
from blacksheep.server.routing import Route
from blacksheep.server.websocket import WebSocket

from .bindings import (
Binder,
BodyBinder,
BoundValue,
ControllerBinder,
ExactBinder,
IdentityBinder,
JSONBinder,
QueryBinder,
RequestBinder,
RouteBinder,
ServiceBinder,
WebSocketBinder,
empty,
get_binder_by_type,
)
Expand Down Expand Up @@ -282,14 +278,8 @@ def _get_parameter_binder(
) -> Binder:
name = parameter.name

if name == "request":
return RequestBinder()

if name == "websocket":
return WebSocketBinder()

if name == "services":
return ExactBinder(services)
if name in Binder.aliases:
return Binder.aliases[name](services)

original_annotation = parameter.annotation

Expand All @@ -302,11 +292,8 @@ def _get_parameter_binder(
if isinstance(annotation, (str, ForwardRef)): # pragma: no cover
raise UnsupportedForwardRefInSignatureError(original_annotation)

if annotation is Request:
return RequestBinder()

if annotation is WebSocket:
return WebSocketBinder()
if annotation in Binder.aliases:
return Binder.aliases[annotation](services)

# 1. is the type annotation of BoundValue[T] type?
if _is_bound_value_annotation(annotation):
Expand Down
5 changes: 5 additions & 0 deletions blacksheep/server/openapi/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ def router_to_paths_dict(
routes_dictionary: Dict[str, Dict[str, T]] = {}

for method, routes in router.routes.items():
if b"_" in method:
# Non standard method, used internally to support more scenarios.
# This is used for WebSocket.
continue

for route in routes:
key = route.mustache_pattern

Expand Down
52 changes: 29 additions & 23 deletions blacksheep/server/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@
from blacksheep.utils import ensure_bytes, ensure_str

__all__ = [
"HTTPMethod",
"Router",
"Route",
"RouteMatch",
"RouteDuplicate",
"RegisteredRoute",
"RoutesRegistry",
"HTTPMethod",
"RouteMethod",
]


class HTTPMethod:
class RouteMethod:
GET = "GET"
GET_WS = "GET_WS"
HEAD = "HEAD"
POST = "POST"
PUT = "PUT"
Expand All @@ -31,6 +33,10 @@ class HTTPMethod:
PATCH = "PATCH"


# for backward compatibility
HTTPMethod = RouteMethod


_route_all_rx = re.compile(b"\\*")
_route_param_rx = re.compile(b"/:([^/]+)")
_mustache_route_param_rx = re.compile(b"/{([^}]+)}")
Expand Down Expand Up @@ -295,64 +301,64 @@ def decorator(fn):
return decorator

def add_head(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.HEAD, pattern, handler)
self.add(RouteMethod.HEAD, pattern, handler)

def add_get(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.GET, pattern, handler)
self.add(RouteMethod.GET, pattern, handler)

def add_post(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.POST, pattern, handler)
self.add(RouteMethod.POST, pattern, handler)

def add_put(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.PUT, pattern, handler)
self.add(RouteMethod.PUT, pattern, handler)

def add_delete(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.DELETE, pattern, handler)
self.add(RouteMethod.DELETE, pattern, handler)

def add_trace(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.TRACE, pattern, handler)
self.add(RouteMethod.TRACE, pattern, handler)

def add_options(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.OPTIONS, pattern, handler)
self.add(RouteMethod.OPTIONS, pattern, handler)

def add_connect(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.CONNECT, pattern, handler)
self.add(RouteMethod.CONNECT, pattern, handler)

def add_patch(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.PATCH, pattern, handler)
self.add(RouteMethod.PATCH, pattern, handler)

def add_ws(self, pattern: str, handler: Callable[..., Any]) -> None:
self.add(HTTPMethod.GET, pattern, handler)
self.add(RouteMethod.GET_WS, pattern, handler)

def head(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.HEAD, pattern)
return self.get_decorator(RouteMethod.HEAD, pattern)

def get(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.GET, pattern)
return self.get_decorator(RouteMethod.GET, pattern)

def post(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.POST, pattern)
return self.get_decorator(RouteMethod.POST, pattern)

def put(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.PUT, pattern)
return self.get_decorator(RouteMethod.PUT, pattern)

def delete(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.DELETE, pattern)
return self.get_decorator(RouteMethod.DELETE, pattern)

def trace(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.TRACE, pattern)
return self.get_decorator(RouteMethod.TRACE, pattern)

def options(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.OPTIONS, pattern)
return self.get_decorator(RouteMethod.OPTIONS, pattern)

def connect(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.CONNECT, pattern)
return self.get_decorator(RouteMethod.CONNECT, pattern)

def patch(self, pattern: Optional[str] = "/") -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.PATCH, pattern)
return self.get_decorator(RouteMethod.PATCH, pattern)

def ws(self, pattern) -> Callable[..., Any]:
return self.get_decorator(HTTPMethod.GET, pattern)
return self.get_decorator(RouteMethod.GET_WS, pattern)


class Router(RouterBase):
Expand Down Expand Up @@ -444,7 +450,7 @@ def get_match(self, method: AnyStr, value: AnyStr) -> Optional[RouteMatch]:
return RouteMatch(self._fallback, None)

def get_ws_match(self, value: AnyStr) -> Optional[RouteMatch]:
return self.get_match(HTTPMethod.GET, value)
return self.get_match(RouteMethod.GET_WS, value)

@lru_cache(maxsize=1200)
def get_matching_route(self, method: AnyStr, value: AnyStr) -> Optional[Route]:
Expand Down
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def readme():

setup(
name="blacksheep",
version="1.2.2",
version="1.2.3",
description="Fast web framework and HTTP client for Python asyncio",
long_description=readme(),
long_description_content_type="text/markdown",
Expand Down Expand Up @@ -52,7 +52,9 @@ def readme():
],
ext_modules=[
Extension(
"blacksheep.url", ["blacksheep/url.c"], extra_compile_args=COMPILE_ARGS
"blacksheep.url",
["blacksheep/url.c"],
extra_compile_args=COMPILE_ARGS,
),
Extension(
"blacksheep.exceptions",
Expand Down Expand Up @@ -107,6 +109,7 @@ def readme():
"full": [
"cryptography~=35.0.0",
"PyJWT~=2.3.0",
"websockets~=10.1",
]
},
include_package_data=True,
Expand Down
Loading

0 comments on commit ab6355d

Please sign in to comment.