Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for 2.0.7 #483

Merged
merged 6 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = E203, E266, W503
ignore = E203, E266, W503, E704, E701
max-line-length = 88
max-complexity = 18
per-file-ignores =
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install black isort flake8
pip install black==24.2.0 isort==5.13.2 flake8==7.0.0

- name: Compile Cython extensions
run: |
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ 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).

## [2.0.7] - 2024-02-17 :tulip:

- Fixes bug [#38](https://github.com/Neoteroi/BlackSheep-Docs/issues/38),
to support properly `list[T]` and `tuple[T]` when defining query string
parameters. Reported by @ranggakd.
- Passes annotated origin type to build OpenAPI docs (#475), by @tyzhnenko.
- Fixes #481, disabling signal handling by default to avoid negative side
effects. Handling signals is now opt-in and can be achieved using the env
variable `APP_SIGNAL_HANDLER=1`. The `is_stopping` function is modified to
work only when the option is enabled. Issue reported by @netanel-haber.
- Upgrades `black` version and format files accordingly.

## [2.0.6] - 2024-01-17 :kr: :heart:

- Adds built-in support for [Server-Sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
Expand Down
3 changes: 2 additions & 1 deletion blacksheep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
Root module of the framework. This module re-exports the most commonly
used types to reduce the verbosity of the imports statements.
"""

__author__ = "Roberto Prevato <[email protected]>"
__version__ = "2.0.6"
__version__ = "2.0.7"

from .contents import Content as Content
from .contents import FormContent as FormContent
Expand Down
1 change: 1 addition & 0 deletions blacksheep/baseapp.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class BaseApplication:
self.router = router
self.exceptions_handlers = self.init_exceptions_handlers()
self.show_error_details = show_error_details

def init_exceptions_handlers(self) -> ExceptionHandlersType: ...
async def handle(self, request: Request) -> Response: ...
async def handle_internal_server_error(
Expand Down
1 change: 1 addition & 0 deletions blacksheep/common/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Common types annotations and functions.
"""

from typing import AnyStr, Dict, Iterable, List, Optional, Tuple, Union

from blacksheep.url import URL
Expand Down
5 changes: 5 additions & 0 deletions blacksheep/contents.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ class Content:
self.type = content_type
self.body = data
self.length = len(data)

async def read(self) -> bytes:
return self.body

def dispose(self) -> None: ...

class StreamedContent(Content):
Expand All @@ -31,6 +33,7 @@ class StreamedContent(Content):
self.body = None
self.length = data_length
self.generator = data_provider

async def get_parts(self) -> AsyncIterable[bytes]: ...

class ASGIContent(Content):
Expand All @@ -39,6 +42,7 @@ class ASGIContent(Content):
self.body = None
self.length = -1
self.receive = receive

def dispose(self): ...
async def stream(self) -> AsyncIterable[bytes]: ...
async def read(self) -> bytes: ...
Expand Down Expand Up @@ -85,6 +89,7 @@ class FormPart:
self.file_name = file_name
self.content_type = content_type
self.charset = charset

def __repr__(self):
return f"<FormPart {self.name} - at {id(self)}>"

Expand Down
1 change: 1 addition & 0 deletions blacksheep/cookies.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Cookie:
self.secure = secure
self.max_age = max_age
self.same_site = same_site

def clone(self) -> "Cookie": ...
def __eq__(self, other: Union[str, bytes, "Cookie"]) -> bool: ...
def __repr__(self) -> str:
Expand Down
2 changes: 2 additions & 0 deletions blacksheep/headers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Header:
def __init__(self, name: bytes, value: bytes):
self.name = name
self.value = value

def __repr__(self) -> str: ...
def __iter__(self) -> Generator[bytes, None, None]: ...
def __eq__(self, other: object) -> bool: ...
Expand All @@ -14,6 +15,7 @@ HeaderType = Tuple[bytes, bytes]
class Headers:
def __init__(self, values: Optional[List[HeaderType]] = None):
self.values = values

def get(self, name: bytes) -> Tuple[HeaderType]: ...
def get_tuples(self, name: bytes) -> List[HeaderType]: ...
def get_first(self, key: bytes) -> Optional[bytes]: ...
Expand Down
4 changes: 4 additions & 0 deletions blacksheep/messages.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ class Message:
otherwise kept as raw bytes.
In case of ambiguity, use the dedicated `multiparts()` method.
"""

async def multipart(self) -> List[FormPart]:
"""
Returns parts read from multipart/form-data, if present, otherwise
None
"""

def declares_content_type(self, type: bytes) -> bool: ...
def declares_json(self) -> bool: ...
def declares_xml(self) -> bool: ...
Expand All @@ -68,6 +70,7 @@ class Request(Message):
self.user: Optional[Identity] = ...
self.scope: ASGIScopeInterface = ...
self._session: Optional[Session]

@classmethod
def incoming(
cls, method: str, path: bytes, query: bytes, headers: List[HeaderType]
Expand Down Expand Up @@ -143,6 +146,7 @@ class Response(Message):
) -> None:
self.status = status
self.content = content

def __repr__(self) -> str: ...
@property
def cookies(self) -> Cookies: ...
Expand Down
1 change: 1 addition & 0 deletions blacksheep/server/authentication/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
This module provides classes to handle OpenID Connect authentication through integration
with OAuth applications, supporting Authorization Code Grant and Hybrid flows.
"""

import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
Expand Down
15 changes: 7 additions & 8 deletions blacksheep/server/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
See:
https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.2
"""

from abc import abstractmethod
from base64 import urlsafe_b64decode
from collections.abc import Iterable as IterableAbc
Expand Down Expand Up @@ -442,8 +443,8 @@ def _get_default_converter_single(self, expected_type):
if expected_type is bytes:
# note: the code is optimized for strings here, not bytes
# since most of times the user will want to handle strings
return (
lambda value: urlsafe_b64decode(value.encode("utf8")).decode("utf8")
return lambda value: (
urlsafe_b64decode(value.encode("utf8")).decode("utf8")
if value
else None
)
Expand Down Expand Up @@ -645,8 +646,8 @@ def _get_default_converter_single(self, expected_type):
return lambda value: dateutil_parser(unquote(value)) if value else None

if expected_type is date:
return (
lambda value: dateutil_parser(unquote(value)).date() if value else None
return lambda value: (
dateutil_parser(unquote(value)).date() if value else None
)

raise MissingConverterError(expected_type, self.__class__)
Expand Down Expand Up @@ -684,10 +685,8 @@ def _get_default_converter(self, expected_type):
return lambda value: dateutil_parser(unquote(value[0])) if value else None

if expected_type is date:
return (
lambda value: dateutil_parser(unquote(value[0])).date()
if value
else None
return lambda value: (
dateutil_parser(unquote(value[0])).date() if value else None
)

raise MissingConverterError(expected_type, self.__class__)
Expand Down
2 changes: 1 addition & 1 deletion blacksheep/server/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ def __init__(self) -> None:
self.show_error_details = truthy(os.environ.get("APP_SHOW_ERROR_DETAILS", ""))
self.mount_auto_events = truthy(os.environ.get("APP_MOUNT_AUTO_EVENTS", "1"))
self.use_default_router = truthy(os.environ.get("APP_DEFAULT_ROUTER", "1"))
self.add_signal_handler = truthy(os.environ.get("APP_SIGNAL_HANDLER", "1"))
self.add_signal_handler = truthy(os.environ.get("APP_SIGNAL_HANDLER", ""))
20 changes: 18 additions & 2 deletions blacksheep/server/normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ def ensure_response(result) -> Response:
return result


class NormalizationError(Exception):
...
class NormalizationError(Exception): ...


class UnsupportedSignatureError(NormalizationError):
Expand Down Expand Up @@ -238,6 +237,23 @@ def __init__(self, parameter_name, route):
Tuple[UUID],
}

try:
# Note: try catch here is to support Python 3.8
# it can be removed when support for Python 3.8 is dropped
_types_handled_with_query |= {
list[str],
list[int],
list[float],
list[bool],
list[UUID],
tuple[str],
tuple[int],
tuple[float],
tuple[bool],
}
except TypeError:
pass


def _check_union(
parameter: ParamInfo, annotation: Any, method: Callable[..., Any]
Expand Down
1 change: 1 addition & 0 deletions blacksheep/server/openapi/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
OpenAPI Documentation v2 and v3 (currently only v3 is supported) from these types, and
potentially in the future v4, if it will be so different from v3.
"""

import json
from abc import ABC, abstractmethod
from dataclasses import dataclass
Expand Down
19 changes: 10 additions & 9 deletions blacksheep/server/openapi/docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* tests/test_openapi_docstrings.py

"""

import re
import warnings
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -212,9 +213,9 @@ def parse_docstring(self, docstring: str) -> DocstringInfo:
description=collapse(description),
parameters=self.get_parameters_info(docstring),
return_type=return_type,
return_description=collapse(return_description)
if return_description
else None,
return_description=(
collapse(return_description) if return_description else None
),
)


Expand Down Expand Up @@ -401,9 +402,9 @@ def parse_docstring(self, docstring: str) -> DocstringInfo:
description=collapse(info.description),
parameters=self.get_parameters_info(docstring),
return_type=return_info.return_type if return_info else None,
return_description=collapse(return_info.return_description)
if return_info
else None,
return_description=(
collapse(return_info.return_description) if return_info else None
),
)


Expand Down Expand Up @@ -482,9 +483,9 @@ def parse_docstring(self, docstring: str) -> DocstringInfo:
description=collapse(info.description),
parameters=self.get_parameters_info(docstring),
return_type=return_info.return_type if return_info else None,
return_description=collapse(return_info.return_description)
if return_info
else None,
return_description=(
collapse(return_info.return_description) if return_info else None
),
)


Expand Down
3 changes: 1 addition & 2 deletions blacksheep/server/openapi/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ def get_ui_handler(self) -> Callable[[Request], Response]:
"""

@property
def default_ui_files(self) -> UIFilesOptions:
...
def default_ui_files(self) -> UIFilesOptions: ...


class SwaggerUIProvider(UIProvider):
Expand Down
20 changes: 11 additions & 9 deletions blacksheep/server/openapi/v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,9 @@ def __init__(
DataClassTypeHandler(),
PydanticModelTypeHandler(),
]
self._binder_docs: Dict[
Type[Binder], Iterable[Union[Parameter, Reference]]
] = {}
self._binder_docs: Dict[Type[Binder], Iterable[Union[Parameter, Reference]]] = (
{}
)
self.security_schemes = security_schemes or {}

@property
Expand Down Expand Up @@ -915,9 +915,11 @@ def get_parameters(
param_info.source or ParameterSource.QUERY
),
required=param_info.required,
schema=self.get_schema_by_type(param_info.value_type)
if param_info.value_type
else None,
schema=(
self.get_schema_by_type(param_info.value_type)
if param_info.value_type
else None
),
description=param_info.description,
example=param_info.example,
)
Expand Down Expand Up @@ -975,9 +977,9 @@ def _get_content_from_response_info(

for content in response_content:
if content.content_type not in oad_content:
oad_content[
content.content_type
] = self._get_media_type_from_content_doc(content)
oad_content[content.content_type] = (
self._get_media_type_from_content_doc(content)
)
else:
raise DuplicatedContentTypeDocsException(content.content_type)

Expand Down
8 changes: 8 additions & 0 deletions blacksheep/server/process.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""
Provides functions related to the server process.
"""

import os
import signal
from typing import TYPE_CHECKING

from blacksheep.utils import truthy

if TYPE_CHECKING:
from blacksheep.server.application import Application

Expand All @@ -15,6 +19,10 @@ def is_stopping() -> bool:
Returns a value indicating whether the server process received a SIGINT or a SIGTERM
signal, and therefore the application is stopping.
"""
if not truthy(os.environ.get("APP_SIGNAL_HANDLER", "")):
raise RuntimeError(
"This function can only be used if the env variable `APP_SIGNAL_HANDLER=1`"
)
return _STOPPING


Expand Down
1 change: 1 addition & 0 deletions blacksheep/server/sse.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
This module offer built-in functions for Server Sent Events.
"""

from typing import AsyncIterable, Callable, List, Optional, Tuple

from blacksheep.contents import ServerSentEvent, StreamedContent
Expand Down
6 changes: 3 additions & 3 deletions blacksheep/testing/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ async def __call__(self):
return {
"body": message,
"type": "http.message",
"more_body": False
if (len(self.messages) == self.index or not message)
else True,
"more_body": (
False if (len(self.messages) == self.index or not message) else True
),
}


Expand Down
Loading
Loading