Skip to content

Commit a8c09b2

Browse files
authored
Merge pull request #21 from eadwinCode/configuration_file_to_class
Moved Configuration From File To Class Based
2 parents d636361 + 8d741d5 commit a8c09b2

21 files changed

+249
-134
lines changed

ellar/core/conf/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .config import Config
2+
from .mixins import ConfigDefaultTypesMixin
23

3-
__all__ = ["Config"]
4+
__all__ = ["Config", "ConfigDefaultTypesMixin"]

ellar/core/conf/app_settings_models.py

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,15 @@
22

33
from pydantic import Field, root_validator, validator
44
from pydantic.json import ENCODERS_BY_TYPE
5-
from starlette.middleware import Middleware
65
from starlette.responses import JSONResponse
76

8-
from ellar.core.events import EventHandler
9-
from ellar.core.versioning import BaseAPIVersioning, DefaultAPIVersioning
7+
from ellar.core.versioning import DefaultAPIVersioning
108
from ellar.serializer import Serializer, SerializerFilter
119

10+
from .mixins import ConfigDefaultTypesMixin, TEventHandler, TMiddleware, TVersioning
1211

13-
class TVersioning(BaseAPIVersioning):
14-
@classmethod
15-
def __get_validators__(
16-
cls: t.Type["TVersioning"],
17-
) -> t.Iterable[t.Callable[..., t.Any]]:
18-
yield cls.validate
1912

20-
@classmethod
21-
def validate(cls: t.Type["BaseAPIVersioning"], v: t.Any) -> t.Any:
22-
if not isinstance(v, BaseAPIVersioning):
23-
raise ValueError(f"Expected BaseAPIVersioning, received: {type(v)}")
24-
return v
25-
26-
27-
class TMiddleware(Middleware):
28-
@classmethod
29-
def __get_validators__(
30-
cls: t.Type["TMiddleware"],
31-
) -> t.Iterable[t.Callable[..., t.Any]]:
32-
yield cls.validate
33-
34-
@classmethod
35-
def validate(cls: t.Type["Middleware"], v: t.Any) -> t.Any:
36-
if not isinstance(v, Middleware):
37-
raise ValueError(f"Expected Middleware, received: {type(v)}")
38-
return v
39-
40-
41-
class TEventHandler(EventHandler):
42-
@classmethod
43-
def __get_validators__(
44-
cls: t.Type["TEventHandler"],
45-
) -> t.Iterable[t.Callable[..., t.Any]]:
46-
yield cls.validate
47-
48-
@classmethod
49-
def validate(cls: t.Type["EventHandler"], v: t.Any) -> t.Any:
50-
if not isinstance(v, EventHandler):
51-
raise ValueError(f"Expected EventHandler, received: {type(v)}")
52-
return v
53-
54-
55-
class ConfigValidationSchema(Serializer):
13+
class ConfigValidationSchema(Serializer, ConfigDefaultTypesMixin):
5614
_filter = SerializerFilter(
5715
exclude={
5816
"EXCEPTION_HANDLERS_DECORATOR",

ellar/core/conf/config.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
import importlib
21
import typing as t
32

43
from starlette.config import environ
54

65
from ellar.compatible.dict import AttributeDictAccessMixin, DataMutableMapper
76
from ellar.constants import ELLAR_CONFIG_MODULE
7+
from ellar.helper.importer import import_from_string
88
from ellar.types import VT
99

10-
from . import default_settings
1110
from .app_settings_models import ConfigValidationSchema
11+
from .default_settings import ConfigDefaults
12+
from .mixins import ConfigDefaultTypesMixin
1213

1314

1415
class ConfigRuntimeError(RuntimeError):
1516
pass
1617

1718

18-
class Config(DataMutableMapper, AttributeDictAccessMixin):
19+
class Config(DataMutableMapper, AttributeDictAccessMixin, ConfigDefaultTypesMixin):
1920
__slots__ = ("config_module", "_data")
2021

2122
def __init__(
@@ -30,16 +31,16 @@ def __init__(
3031
self.config_module = config_module or environ.get(ELLAR_CONFIG_MODULE, None)
3132

3233
self._data.clear()
33-
for setting in dir(default_settings):
34+
for setting in dir(ConfigDefaults):
3435
if setting.isupper():
35-
self._data[setting] = getattr(default_settings, setting)
36+
self._data[setting] = ConfigDefaults.__dict__[setting]
3637

3738
if self.config_module:
3839
try:
39-
mod = importlib.import_module(self.config_module)
40+
mod = import_from_string(self.config_module)
4041
for setting in dir(mod):
4142
if setting.isupper():
42-
self._data[setting] = getattr(mod, setting)
43+
self._data[setting] = mod.__dict__[setting]
4344
except Exception as ex:
4445
raise ConfigRuntimeError(str(ex))
4546

ellar/core/conf/default_settings.py

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from pydantic.json import ENCODERS_BY_TYPE
44
from starlette.exceptions import HTTPException as StarletteHTTPException
5-
from starlette.middleware import Middleware
65
from starlette.responses import JSONResponse
76
from starlette.types import ASGIApp
87
from starlette.websockets import WebSocketClose
@@ -12,44 +11,15 @@
1211
request_validation_exception_handler,
1312
)
1413
from ellar.core.response import PlainTextResponse
15-
from ellar.core.versioning import BaseAPIVersioning, DefaultAPIVersioning
14+
from ellar.core.versioning import DefaultAPIVersioning
1615
from ellar.exceptions import APIException, RequestValidationError
1716
from ellar.types import TReceive, TScope, TSend
1817

18+
from .mixins import ConfigDefaultTypesMixin, TMiddleware, TVersioning
19+
1920
if t.TYPE_CHECKING: # pragma: no cover
2021
from ellar.core.main import App
2122

22-
DEBUG: bool = False
23-
24-
DEFAULT_JSON_CLASS: t.Type[JSONResponse] = JSONResponse
25-
SECRET_KEY: str = "your-secret-key"
26-
27-
# injector auto_bind = True allows you to resolve types that are not registered on the container
28-
# For more info, read: https://injector.readthedocs.io/en/latest/index.html
29-
INJECTOR_AUTO_BIND = False
30-
31-
JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {}
32-
33-
VERSIONING_SCHEME: BaseAPIVersioning = DefaultAPIVersioning()
34-
REDIRECT_SLASHES: bool = False
35-
36-
STATIC_FOLDER_PACKAGES: t.Optional[t.List[t.Union[str, t.Tuple[str]]]] = []
37-
STATIC_DIRECTORIES: t.Optional[t.List[t.Union[str, t.Any]]] = []
38-
STATIC_MOUNT_PATH: str = "/static"
39-
40-
MIDDLEWARE: t.Sequence[Middleware] = []
41-
42-
APP_EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable] = {
43-
RequestValidationError: request_validation_exception_handler,
44-
APIException: api_exception_handler,
45-
}
46-
47-
# A dictionary mapping either integer status codes,
48-
# or exception class types onto callables which handle the exceptions.
49-
# Exception handler callables should be of the form
50-
# `handler(request, exc) -> response` and may be be either standard functions, or async functions.
51-
USER_CUSTOM_EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable] = {}
52-
5323

5424
async def _not_found(scope: TScope, receive: TReceive, send: TSend) -> None:
5525
if scope["type"] == "websocket":
@@ -67,10 +37,48 @@ async def _not_found(scope: TScope, receive: TReceive, send: TSend) -> None:
6737
await response(scope, receive, send)
6838

6939

70-
DEFAULT_NOT_FOUND_HANDLER: ASGIApp = _not_found
71-
# The lifespan context function is a newer style that replaces
72-
# on_startup / on_shutdown handlers. Use one or the other, not both.
73-
DEFAULT_LIFESPAN_HANDLER: t.Optional[t.Callable[["App"], t.AsyncContextManager]] = None
40+
class ConfigDefaults(ConfigDefaultTypesMixin):
41+
DEBUG: bool = False
42+
43+
DEFAULT_JSON_CLASS: t.Type[JSONResponse] = JSONResponse
44+
SECRET_KEY: str = "your-secret-key"
45+
46+
# injector auto_bind = True allows you to resolve types that are not registered on the container
47+
# For more info, read: https://injector.readthedocs.io/en/latest/index.html
48+
INJECTOR_AUTO_BIND = False
49+
50+
JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {}
51+
52+
VERSIONING_SCHEME: TVersioning = DefaultAPIVersioning() # type: ignore
53+
REDIRECT_SLASHES: bool = False
54+
55+
STATIC_FOLDER_PACKAGES: t.Optional[t.List[t.Union[str, t.Tuple[str, str]]]] = []
56+
STATIC_DIRECTORIES: t.Optional[t.List[t.Union[str, t.Any]]] = []
57+
STATIC_MOUNT_PATH: str = "/static"
58+
59+
MIDDLEWARE: t.List[TMiddleware] = []
60+
61+
APP_EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable] = {
62+
RequestValidationError: request_validation_exception_handler,
63+
APIException: api_exception_handler,
64+
}
65+
66+
# A dictionary mapping either integer status codes,
67+
# or exception class types onto callables which handle the exceptions.
68+
# Exception handler callables should be of the form
69+
# `handler(request, exc) -> response` and may be be either standard functions, or async functions.
70+
USER_CUSTOM_EXCEPTION_HANDLERS: t.Dict[
71+
t.Union[int, t.Type[Exception]], t.Callable
72+
] = {}
73+
74+
DEFAULT_NOT_FOUND_HANDLER: ASGIApp = _not_found
75+
# The lifespan context function is a newer style that replaces
76+
# on_startup / on_shutdown handlers. Use one or the other, not both.
77+
DEFAULT_LIFESPAN_HANDLER: t.Optional[
78+
t.Callable[["App"], t.AsyncContextManager]
79+
] = None
7480

75-
# Object Serializer custom encoders
76-
SERIALIZER_CUSTOM_ENCODER: t.Dict[t.Any, t.Callable[[t.Any], t.Any]] = ENCODERS_BY_TYPE
81+
# Object Serializer custom encoders
82+
SERIALIZER_CUSTOM_ENCODER: t.Dict[
83+
t.Any, t.Callable[[t.Any], t.Any]
84+
] = ENCODERS_BY_TYPE

ellar/core/conf/mixins.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import typing as t
2+
3+
from starlette.middleware import Middleware
4+
from starlette.responses import JSONResponse
5+
6+
from ellar.core.events import EventHandler
7+
from ellar.core.versioning import BaseAPIVersioning
8+
9+
__all__ = ["ConfigDefaultTypesMixin", "TVersioning", "TMiddleware", "TEventHandler"]
10+
11+
12+
class TVersioning(BaseAPIVersioning):
13+
@classmethod
14+
def __get_validators__(
15+
cls: t.Type["TVersioning"],
16+
) -> t.Iterable[t.Callable[..., t.Any]]:
17+
yield cls.validate
18+
19+
@classmethod
20+
def validate(cls: t.Type["BaseAPIVersioning"], v: t.Any) -> t.Any:
21+
if not isinstance(v, BaseAPIVersioning):
22+
raise ValueError(f"Expected BaseAPIVersioning, received: {type(v)}")
23+
return v
24+
25+
26+
class TMiddleware(Middleware):
27+
@classmethod
28+
def __get_validators__(
29+
cls: t.Type["TMiddleware"],
30+
) -> t.Iterable[t.Callable[..., t.Any]]:
31+
yield cls.validate
32+
33+
@classmethod
34+
def validate(cls: t.Type["Middleware"], v: t.Any) -> t.Any:
35+
if not isinstance(v, Middleware):
36+
raise ValueError(f"Expected Middleware, received: {type(v)}")
37+
return v
38+
39+
40+
class TEventHandler(EventHandler):
41+
@classmethod
42+
def __get_validators__(
43+
cls: t.Type["TEventHandler"],
44+
) -> t.Iterable[t.Callable[..., t.Any]]:
45+
yield cls.validate
46+
47+
@classmethod
48+
def validate(cls: t.Type["EventHandler"], v: t.Any) -> t.Any:
49+
if not isinstance(v, EventHandler):
50+
raise ValueError(f"Expected EventHandler, received: {type(v)}")
51+
return v
52+
53+
54+
class ConfigDefaultTypesMixin:
55+
DEBUG: bool
56+
INJECTOR_AUTO_BIND: bool
57+
DEFAULT_JSON_CLASS: t.Type[JSONResponse]
58+
59+
JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any]
60+
VERSIONING_SCHEME: TVersioning
61+
62+
REDIRECT_SLASHES: bool
63+
STATIC_FOLDER_PACKAGES: t.Optional[t.List[t.Union[str, t.Tuple[str, str]]]]
64+
STATIC_DIRECTORIES: t.Optional[t.List[t.Union[str, t.Any]]]
65+
66+
MIDDLEWARE: t.List[TMiddleware]
67+
APP_EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable]
68+
69+
USER_CUSTOM_EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable]
70+
EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable]
71+
STATIC_MOUNT_PATH: str
72+
73+
SERIALIZER_CUSTOM_ENCODER: t.Dict[t.Any, t.Callable[[t.Any], t.Any]]
74+
75+
MIDDLEWARE_DECORATOR: t.List[TMiddleware]
76+
77+
ON_REQUEST_STARTUP: t.List[TEventHandler]
78+
ON_REQUEST_SHUTDOWN: t.List[TEventHandler]
79+
80+
TEMPLATE_FILTERS: t.Dict[str, t.Callable[..., t.Any]]
81+
TEMPLATE_GLOBAL_FILTERS: t.Dict[str, t.Callable[..., t.Any]]

ellar/core/factory.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,7 @@
44

55
from starlette.routing import Host, Mount
66

7-
from ellar.constants import (
8-
MODULE_METADATA,
9-
MODULE_WATERMARK,
10-
ON_REQUEST_SHUTDOWN_KEY,
11-
ON_REQUEST_STARTUP_KEY,
12-
)
7+
from ellar.constants import MODULE_METADATA, MODULE_WATERMARK
138
from ellar.core import Config
149
from ellar.core.main import App
1510
from ellar.core.modules import ModuleBase
@@ -71,11 +66,11 @@ def _create_app(
7166
assert reflect.get_metadata(MODULE_WATERMARK, module), "Only Module is allowed"
7267

7368
config = Config(app_configured=True, config_module=config_module)
74-
injector = EllarInjector(auto_bind=t.cast(bool, config.INJECTOR_AUTO_BIND))
69+
injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND)
7570
cls._build_modules(app_module=module, injector=injector, config=config)
7671

77-
shutdown_event = config[ON_REQUEST_STARTUP_KEY]
78-
startup_event = config[ON_REQUEST_SHUTDOWN_KEY]
72+
shutdown_event = config.ON_REQUEST_STARTUP
73+
startup_event = config.ON_REQUEST_SHUTDOWN
7974

8075
app = App(
8176
config=config,

ellar/core/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def __init__(
7373
)
7474
self.router = ApplicationRouter(
7575
routes=self._get_module_routes(),
76-
redirect_slashes=t.cast(bool, self.config.REDIRECT_SLASHES),
76+
redirect_slashes=self.config.REDIRECT_SLASHES,
7777
on_startup=[self.on_startup.async_run],
7878
on_shutdown=[self.on_shutdown.async_run],
7979
default=self.config.DEFAULT_NOT_FOUND_HANDLER, # type: ignore

ellar/core/params/args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pydantic.error_wrappers import ErrorWrapper
88
from pydantic.fields import FieldInfo, ModelField, Required
99
from pydantic.schema import get_annotation_from_field_info
10-
from pydantic.typing import ForwardRef, evaluate_forwardref
10+
from pydantic.typing import ForwardRef, evaluate_forwardref # type: ignore
1111
from pydantic.utils import lenient_issubclass
1212
from starlette.convertors import Convertor
1313

ellar/core/templating/interface.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def get_module_loaders(self) -> t.Generator[ModuleTemplating, None, None]:
143143

144144
@property
145145
def debug(self) -> bool:
146-
return t.cast(bool, self.config.DEBUG)
146+
return self.config.DEBUG
147147

148148
@debug.setter
149149
def debug(self, value: bool) -> None:
@@ -193,7 +193,7 @@ def create_global_jinja_loader(self) -> JinjaLoader:
193193

194194
def create_static_app(self) -> ASGIApp:
195195
return StaticFiles(
196-
directories=self.static_files, packages=self.config.STATIC_FOLDER_PACKAGES # type: ignore
196+
directories=self.static_files, packages=self.config.STATIC_FOLDER_PACKAGES
197197
)
198198

199199
def reload_static_app(self) -> None:

0 commit comments

Comments
 (0)