Skip to content

Commit

Permalink
V2 (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsil authored Jul 24, 2023
2 parents d1ad06b + 003d88f commit 11dd4af
Show file tree
Hide file tree
Showing 139 changed files with 1,266 additions and 558 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
- uses: "actions/setup-python@v4"
with:
python-version: "${{ matrix.python-version }}"
# allow-prereleases: true
- uses: actions/cache@v3
id: cache
with:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ target/
*.iml
.DS_Store
.coverage
.coverage.*
.python-version
coverage.*
example.sqlite
example.sqlite
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ default_language_version:
python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-toml
- id: check-yaml
args:
- --unsafe
- id: end-of-file-fixer
- id: debug-statements
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
Expand Down
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ test: ## Runs the tests
coverage: ## Run tests and coverage
ESMERALD_SETTINGS_MODULE='tests.settings.TestSettings' pytest --cov=esmerald --cov=tests --cov-report=term-missing:skip-covered --cov-report=html tests

# .PHONY: cov
# cov: ## Run tests and coverage only for specific ones
# ESMERALD_SETTINGS_MODULE='tests.settings.TestSettings' pytest --cov=esmerald --cov=${ONLY} --cov-report=term-missing:skip-covered --cov-report=html ${ONLY}

.PHONY: cov
cov: ## Run tests and coverage only for specific ones
ESMERALD_SETTINGS_MODULE='tests.settings.TestSettings' coverage run -m pytest tests
ESMERALD_SETTINGS_MODULE='tests.settings.TestSettings' coverage combine
ESMERALD_SETTINGS_MODULE='tests.settings.TestSettings' coverage report --show-missing
ESMERALD_SETTINGS_MODULE='tests.settings.TestSettings' coverage html


.PHONY: requirements
requirements: ## Install requirements for development
pip install -e .[dev,test,doc,templates,jwt,encoders,schedulers,ipython,ptpython]
Expand Down
20 changes: 13 additions & 7 deletions esmerald/applications.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import timezone as dtimezone
from functools import cached_property
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -61,8 +62,8 @@
from esmerald.utils.helpers import is_class_and_subclass

if TYPE_CHECKING:
from esmerald.conf import EsmeraldLazySettings
from esmerald.types import SettingsType, TemplateConfig
from esmerald.conf import EsmeraldLazySettings # pragma: no cover
from esmerald.types import SettingsType, TemplateConfig # pragma: no cover

AppType = TypeVar("AppType", bound="Esmerald")

Expand Down Expand Up @@ -525,7 +526,7 @@ def get_settings_value(

if local_settings:
setting_value = getattr(local_settings, value, None)
if not setting_value:
if setting_value is None:
return getattr(global_settings, value, None)
return setting_value

Expand Down Expand Up @@ -662,8 +663,15 @@ def add_websocket_route(
)

def add_include(self, include: Include) -> None:
"""Adds an include directly to the active application router"""
"""
Adds an include directly to the active application router
and creates the proper signature models.
"""
self.router.routes.append(include)

for route in include.routes:
self.router.create_signature_models(route)

self.activate_openapi()

def add_child_esmerald(
Expand Down Expand Up @@ -946,8 +954,6 @@ def build_pluggable_stack(self) -> Optional["Esmerald"]:
"An extension must subclass from esmerald.pluggables.Extension and added to "
"a Pluggable object"
)
else:
pluggables[name] = Pluggable(extension)

app: "ASGIApp" = self
for name, pluggable in pluggables.items():
Expand All @@ -971,7 +977,7 @@ def settings(self) -> Type["EsmeraldAPISettings"]:
general_settings = self.settings_config if self.settings_config else esmerald_settings
return cast("Type[EsmeraldAPISettings]", general_settings)

@property
@cached_property
def default_settings(self) -> Union[Type["EsmeraldAPISettings"], Type["EsmeraldLazySettings"]]:
"""
Returns the default global settings.
Expand Down
6 changes: 4 additions & 2 deletions esmerald/conf/global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
)

if TYPE_CHECKING:
from esmerald.routing.router import Include
from esmerald.types import TemplateConfig
from esmerald.routing.router import Include # pragma: no cover
from esmerald.types import TemplateConfig # pragma: no cover


class EsmeraldAPISettings(BaseSettings):
Expand Down Expand Up @@ -72,6 +72,7 @@ class EsmeraldAPISettings(BaseSettings):
)
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/[email protected]/swagger-ui.min.css"
swagger_favicon_url: str = "https://esmerald.dev/statics/images/favicon.ico"
with_google_fonts: bool = True

# Model configuration
model_config = SettingsConfigDict(extra="allow", ignored_types=(cached_property,))
Expand Down Expand Up @@ -279,6 +280,7 @@ def openapi_config(self) -> OpenAPIConfig:
root_path_in_servers=self.root_path_in_servers,
openapi_version=self.openapi_version,
openapi_url=self.openapi_url,
with_google_fonts=self.with_google_fonts,
)

@property
Expand Down
8 changes: 5 additions & 3 deletions esmerald/config/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class OpenAPIConfig(BaseModel):
swagger_js_url: Optional[str] = None
swagger_css_url: Optional[str] = None
swagger_favicon_url: Optional[str] = None
with_google_fonts: bool = True

def openapi(self, app: Any) -> Dict[str, Any]:
"""Loads the OpenAPI routing schema"""
Expand Down Expand Up @@ -80,7 +81,7 @@ async def _openapi(request: Request) -> JSONResponse:
if self.openapi_url and self.docs_url:

@get(path=self.docs_url)
async def swagger_ui_html(request: Request) -> HTMLResponse:
async def swagger_ui_html(request: Request) -> HTMLResponse: # pragma: no cover
root_path = request.scope.get("root_path", "").rstrip("/")
openapi_url = root_path + self.openapi_url
oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url
Expand All @@ -107,7 +108,7 @@ async def swagger_ui_html(request: Request) -> HTMLResponse:
if self.swagger_ui_oauth2_redirect_url:

@get(self.swagger_ui_oauth2_redirect_url)
async def swagger_ui_redirect(request: Request) -> HTMLResponse:
async def swagger_ui_redirect(request: Request) -> HTMLResponse: # pragma: no cover
return get_swagger_ui_oauth2_redirect_html()

app.add_route(
Expand All @@ -120,14 +121,15 @@ async def swagger_ui_redirect(request: Request) -> HTMLResponse:
if self.openapi_url and self.redoc_url:

@get(self.redoc_url)
async def redoc_html(request: Request) -> HTMLResponse:
async def redoc_html(request: Request) -> HTMLResponse: # pragma: no cover
root_path = request.scope.get("root_path", "").rstrip("/")
openapi_url = root_path + self.openapi_url
return get_redoc_html(
openapi_url=openapi_url,
title=self.title + " - ReDoc",
redoc_js_url=self.redoc_js_url,
redoc_favicon_url=self.redoc_favicon_url,
with_google_fonts=True,
)

app.add_route(
Expand Down
8 changes: 3 additions & 5 deletions esmerald/config/static_files.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple, Union

from pydantic import BaseModel, DirectoryPath, constr, field_validator
from starlette.staticfiles import StaticFiles
from starlette.types import ASGIApp

from esmerald.utils.url import clean_path

if TYPE_CHECKING:
from starlette.types import ASGIApp


class StaticFilesConfig(BaseModel):
path: constr(min_length=1) # type: ignore
Expand Down Expand Up @@ -36,7 +34,7 @@ def _build_kwargs(
kwargs.update({"directory": str(self.directory)}) # type: ignore
return kwargs # type: ignore

def to_app(self) -> "ASGIApp":
def to_app(self) -> ASGIApp:
"""
It can be three scenarios
"""
Expand Down
4 changes: 2 additions & 2 deletions esmerald/contrib/auth/common/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
T = TypeVar("T")


class CommonJWTAuthMiddleware(BaseAuthMiddleware):
class CommonJWTAuthMiddleware(BaseAuthMiddleware): # pragma: no cover
"""
The simple JWT authentication Middleware.
"""

def __init__(
self,
app: "ASGIApp",
app: ASGIApp,
config: "JWTConfig",
user_model: T,
):
Expand Down
4 changes: 1 addition & 3 deletions esmerald/core/urls/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from esmerald.exceptions import ImproperlyConfigured

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.routing.gateways import Gateway, WebSocketGateway
from esmerald.routing.router import Include

Expand Down Expand Up @@ -66,14 +66,12 @@ def include(
pattern (Optional[str], optional): The name of the list to be read from the
module. Defaults to `router_patterns`.
"""

if not isinstance(arg, str):
raise ImproperlyConfigured("The value should be a string with the format <module>.<file>")

router_conf_module = import_module(arg)
pattern = pattern or DEFAULT_PATTERN
patterns = getattr(router_conf_module, pattern, None)

if not patterns:
raise ImproperlyConfigured(
f"There is no pattern {pattern} found in {arg}. "
Expand Down
24 changes: 6 additions & 18 deletions esmerald/datastructures/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,23 @@
from starlette.datastructures import Headers as Headers # noqa: F401
from starlette.datastructures import MutableHeaders as MutableHeaders # noqa
from starlette.datastructures import QueryParams as QueryParams # noqa: F401
from starlette.datastructures import Secret as StarletteSecret # noqa
from starlette.datastructures import State as StarletteStateClass # noqa: F401
from starlette.datastructures import UploadFile as StarletteUploadFile # noqa
from starlette.datastructures import URLPath as URLPath # noqa: F401
from starlette.responses import Response as StarletteResponse # noqa
from typing_extensions import Literal

from esmerald.backgound import BackgroundTask, BackgroundTasks # noqa
from esmerald.enums import MediaType

R = TypeVar("R", bound=StarletteResponse)

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
from esmerald.enums import MediaType


class UploadFile(StarletteUploadFile):
class UploadFile(StarletteUploadFile): # pragma: no cover
"""
Adding pydantic specific functionalitty for parsing.
"""
Expand Down Expand Up @@ -80,25 +81,12 @@ def __get_pydantic_core_schema__(
return general_plain_validator_function(cls._validate)


class Secret:
def __init__(self, value: str):
self._value = value

def __repr__(self) -> str:
class_name = self.__class__.__name__
return f"{class_name}('**********')"

def __str__(self) -> str:
return self._value

def __bool__(self) -> bool:
return bool(self._value)

class Secret(StarletteSecret): # pragma: no cover
def __len__(self) -> int:
return len(self._value)


class State(StarletteStateClass):
class State(StarletteStateClass): # pragma: no cover
state: Dict[str, Any]

def __copy__(self) -> "State":
Expand Down
8 changes: 4 additions & 4 deletions esmerald/datastructures/encoders.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union

from esmerald.datastructures.base import ResponseContainer
from esmerald.enums import MediaType

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
from esmerald.enums import MediaType

try:
from esmerald.responses.encoders import ORJSONResponse
except ImportError:
except ImportError: # pragma: no cover
ORJSONResponse = None # type: ignore

try:
from esmerald.responses.encoders import UJSONResponse
except ImportError:
except ImportError: # pragma: no cover
UJSONResponse = None # type: ignore


Expand Down
4 changes: 2 additions & 2 deletions esmerald/datastructures/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from starlette.responses import FileResponse # noqa

from esmerald.datastructures.base import ResponseContainer
from esmerald.enums import MediaType

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
from esmerald.enums import MediaType


class File(ResponseContainer[FileResponse]):
Expand Down
4 changes: 2 additions & 2 deletions esmerald/datastructures/json.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union

from esmerald.datastructures.base import ResponseContainer # noqa
from esmerald.enums import MediaType
from esmerald.responses import JSONResponse # noqa

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
from esmerald.enums import MediaType


class JSON(ResponseContainer[JSONResponse]):
Expand Down
12 changes: 0 additions & 12 deletions esmerald/datastructures/multidict.py

This file was deleted.

4 changes: 2 additions & 2 deletions esmerald/datastructures/redirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from starlette.responses import RedirectResponse # noqa

from esmerald.datastructures.base import ResponseContainer # noqa
from esmerald.enums import MediaType

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
from esmerald.enums import MediaType


class Redirect(ResponseContainer[RedirectResponse]):
Expand Down
4 changes: 2 additions & 2 deletions esmerald/datastructures/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from starlette.responses import StreamingResponse # noqa

from esmerald.datastructures.base import ResponseContainer # noqa
from esmerald.enums import MediaType

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
from esmerald.enums import MediaType


class Stream(ResponseContainer[StreamingResponse]):
Expand Down
Loading

0 comments on commit 11dd4af

Please sign in to comment.