Skip to content

Commit b71e726

Browse files
committed
Added Configuration documentation and changed allowed_host to ['*'] on debug=True
1 parent ee25a66 commit b71e726

File tree

9 files changed

+323
-25
lines changed

9 files changed

+323
-25
lines changed

docs/configurations.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
2+
The `config.py` file contains all the configuration necessary in bootstrapping ellar application.
3+
4+
Lets in this section go through the different configuration available.
5+
6+
## **Configuration Variables**
7+
### **SECRET_KEY**
8+
Default: `' '` _(Empty string)_
9+
10+
A secret key is a unique and unpredictable value.
11+
12+
`ellar new project` command automatically adds a randomly-generated `SECRET_KEY` to each new project.
13+
14+
### **DEBUG**
15+
Default: `False`
16+
17+
A boolean that turns on/off debug mode.
18+
19+
Never deploy a site into production with `DEBUG` turned `on`.
20+
21+
One of the main features of `debug` mode is the display of detailed error pages.
22+
If your app raises an exception when `DEBUG` is `True`, Ellar will display a detailed traceback.
23+
24+
If `DEBUG` is `False`, you also need to properly set the `ALLOWED_HOSTS` setting. Failing to do so will result in all requests being returned as `“Bad Request (400)”`.
25+
26+
### **INJECTOR_AUTO_BIND**
27+
Default: `False`
28+
29+
A boolean that turns on/off injector `auto_bind` property.
30+
31+
When turned on, `injector` can automatically bind to missing types as `singleton` at the point of resolving object dependencies.
32+
And when turned off, missing types will raise an `UnsatisfiedRequirement` exception.
33+
34+
### **DEFAULT_JSON_CLASS**
35+
Default: `JSONResponse` - (`starlette.response.JSONResponse`)
36+
37+
**DEFAULT_JSON_CLASS** is used when sending JSON response to the client.
38+
39+
There are other options for JSON available in Ellar:
40+
41+
- **UJSONResponse**(`ellar.core.response.UJSONResponse`): renders JSON response using [ujson](https://pypi.python.org/pypi/ujson).
42+
- **ORJSONResponse**(`ellar.core.response.ORJSONResponse`): renders JSON response using [orjson](https://pypi.org/project/orjson/).
43+
44+
### **JINJA_TEMPLATES_OPTIONS**
45+
Default: `{}`
46+
47+
Default is an empty dictionary object. It defines options used when creating `Jinja2` Environment for templating.
48+
49+
Different keys available:
50+
51+
- `block_start_string` (str) –
52+
- `block_end_string` (str) –
53+
- `variable_start_string` (str) –
54+
- `variable_end_string` (str) –
55+
- `comment_start_string` (str) –
56+
- `comment_end_string` (str) –
57+
- `line_statement_prefix` (Optional[str]) –
58+
- `line_comment_prefix` (Optional[str]) –
59+
- `trim_blocks` (bool) –
60+
- `lstrip_blocks` (bool) –
61+
- `newline_sequence` (te.Literal['\n', '\r\n', '\r']) –
62+
- `keep_trailing_newline` (bool) –
63+
- `extensions` (Sequence[Union[str, Type[Extension]]]) –
64+
- `optimized` (bool) –
65+
- `undefined` (Type[jinja2.runtime.Undefined]) –
66+
- `finalize` (Optional[Callable[[...], Any]]) –
67+
- `autoescape` (Union[bool, Callable[[Optional[str]], bool]]) –
68+
- `loader` (Optional[BaseLoader]) –
69+
- `cache_size` (int) –
70+
- `auto_reload` (bool) –
71+
- `bytecode_cache` (Optional[BytecodeCache]) –
72+
- `enable_async` (bool)
73+
74+
!!! info
75+
Check Jinja2 [environment option](https://jinja.palletsprojects.com/en/3.0.x/api/#high-level-api) for more information.
76+
77+
### **VERSIONING_SCHEME**
78+
Default: `DefaultAPIVersioning()`
79+
80+
**VERSIONING_SCHEME** defined the versioning scheme for the application.
81+
The **DefaultAPIVersioning** is placeHolder object for versioning scheme.
82+
83+
Other Options includes:
84+
85+
- **UrlPathAPIVersioning** - for url versioning. eg `https://example.com/v1` or `https://example.com/v2`
86+
- **HostNameAPIVersioning** - for host versioning. eg `https://v1.example.com` or `https://v2.example.com`
87+
- **HeaderAPIVersioning** - for request header versioning. eg `Accept: application/json; version=1.0`
88+
- **QueryParameterAPIVersioning** - for request query versioning. eg `/something/?version=0.1`
89+
90+
### **REDIRECT_SLASHES**
91+
Default: `False`
92+
93+
A boolean that turns on/off router `redirect_slashes` property.
94+
95+
When **REDIRECT_SLASHES** is turned on, the Application Router creates a redirect with a `/` to complete a URL path.
96+
This only happens when the URL was not found but may exist when `/` is appended to the URL.
97+
98+
For example, a route to the user profile goes like this `http://localhost:8000/user/profile/`. If a path like this is passed `http://localhost:8000/user/profile`, it will be redirected to `http://localhost:8000/user/profile` automatically.
99+
100+
This approach may be complex depending on the application size because ApplicationRouter has to loop through its routes twice.
101+
102+
When **REDIRECT_SLASHES** is turned off, URL paths have to be an exact match, or a `404` exception is raised.
103+
104+
### **STATIC_FOLDER_PACKAGES**
105+
Default: `[]`
106+
107+
It is used to apply static files that exist in installed python package.
108+
109+
For example:
110+
111+
```python
112+
STATIC_FOLDER_PACKAGES = [('boostrap4', 'statics')]
113+
```
114+
`'boostrap4'` is the package, and `'statics'` is the static folder.
115+
116+
### **STATIC_DIRECTORIES**
117+
Default: `[]`
118+
119+
It is used to apply static files that project level
120+
121+
For example:
122+
123+
```python
124+
STATIC_DIRECTORIES = ['project_name/staticfiles', 'project_name/path/to/static/files']
125+
```
126+
127+
### **MIDDLEWARE**
128+
Default: `[]`
129+
130+
**MIDDLEWARE** defines a list of user-defined ASGI Middleware to be applied to the application alongside default application middleware.
131+
132+
### **EXCEPTION_HANDLERS**
133+
Default: `[]`
134+
135+
It defines a list of `IExceptionHandler` objects used in handling custom exceptions or any exception.
136+
137+
### **STATIC_MOUNT_PATH**
138+
Default: `/static`
139+
140+
It configures the root path to get to static files. eg `http://localhost:8000/static/stylesheet.css`.
141+
And if for instance `STATIC_MOUNT_PATH`=`'/my-static'`, then the route becomes `http://localhost:8000/my-static/stylesheet.css`
142+
143+
### **SERIALIZER_CUSTOM_ENCODER**
144+
Default: `ENCODERS_BY_TYPE` (`pydantic.json.ENCODERS_BY_TYPE`)
145+
146+
**SERIALIZER_CUSTOM_ENCODER** is a key-value pair of type and function. Default is a pydantic JSON encode type.
147+
It is used when serializing objects to JSON format.
148+
149+
### **DEFAULT_NOT_FOUND_HANDLER**
150+
Default: `not_found` (`not_found(scope: TScope, receive: TReceive, send: TSend)`)
151+
152+
Default is an ASGI function. **DEFAULT_NOT_FOUND_HANDLER** is used by the application router as a callback function to a resource not found.
153+
154+
```python
155+
from ellar.types import TScope, TReceive, TSend
156+
from starlette.exceptions import HTTPException as StarletteHTTPException
157+
from starlette.websockets import WebSocketClose
158+
from ellar.core.response import PlainTextResponse
159+
160+
161+
async def _not_found(scope: TScope, receive: TReceive, send: TSend) -> None:
162+
if scope["type"] == "websocket":
163+
websocket_close = WebSocketClose()
164+
await websocket_close(scope, receive, send)
165+
return
166+
167+
# If we're running inside a starlette application then raise an
168+
# exception, so that the configurable exception handler can deal with
169+
# returning the response. For plain ASGI apps, just return the response.
170+
if "app" in scope:
171+
raise StarletteHTTPException(status_code=404)
172+
else:
173+
response = PlainTextResponse("Not Found", status_code=404)
174+
await response(scope, receive, send)
175+
```
176+
177+
### **DEFAULT_LIFESPAN_HANDLER**
178+
Default: `None`
179+
180+
**DEFAULT_LIFESPAN_HANDLER** is a function that returns `AsyncContextManager` used to manage `startup` and `shutdown`
181+
together instead of having a separate handler for `startup` and `shutdown` events.
182+
183+
```python
184+
import contextlib
185+
from ellar.core import App, ConfigDefaultTypesMixin
186+
187+
188+
@contextlib.asynccontextmanager
189+
async def lifespan(app: App):
190+
async with some_async_resource():
191+
yield
192+
193+
194+
class BaseConfig(ConfigDefaultTypesMixin):
195+
DEFAULT_LIFESPAN_HANDLER = lifespan
196+
```
197+
198+
Consider using `anyio.create_task_group()` for managing asynchronous tasks.
199+
200+
### **CORS_ALLOW_ORIGINS**
201+
Default: `[]`
202+
203+
A list of origins that should be permitted to make cross-origin requests. e.g. `['https://example.org', 'https://www.example.org']`.
204+
205+
You can use `['*']` to allow any origin.
206+
207+
### **CORS_ALLOW_METHODS: t.List[str]**
208+
Default: `["GET"]`
209+
210+
A list of HTTP methods that should be allowed for cross-origin requests.
211+
212+
You can use `['*']` to allow all standard methods.
213+
214+
### **CORS_ALLOW_HEADERS**:
215+
Default: `[]`
216+
217+
A list of HTTP request headers that should be supported for cross-origin requests.
218+
219+
You can use `['*']` to allow all headers. The `Accept`, `Accept-Language`, `Content-Language` and `Content-Type` headers are always allowed for CORS requests.
220+
221+
### **CORS_ALLOW_CREDENTIALS**
222+
Default: `False`
223+
224+
Indicate that cookies should be supported for cross-origin requests.
225+
226+
### **CORS_ALLOW_ORIGIN_REGEX**:
227+
Default: `None`
228+
229+
A regex string to match against origins that should be permitted to make cross-origin requests. eg. `'https://.*\.example\.org'`.
230+
231+
### **CORS_EXPOSE_HEADERS**:
232+
Default: `None`
233+
234+
Indicate any response headers that should be made accessible to the browser.
235+
236+
### **CORS_MAX_AGE:**
237+
Defaults: `600`
238+
239+
Sets a maximum time in seconds for browsers to cache CORS responses.
240+
241+
### **ALLOWED_HOSTS**
242+
Default: `["*"]`
243+
244+
A list of domain names that should be allowed as hostnames in `TrustedHostMiddleware`.
245+
Wildcard domains such as `*.example.com` are supported for matching subdomains.
246+
247+
To allow any hostname either use `allowed_hosts=["*"]` or omit the middleware.
248+
249+
### **REDIRECT_HOST**
250+
Default: `True`
251+
252+
Indicates whether to append `www.` when redirecting host in `TrustedHostMiddleware`

docs/overview/parsing-inputs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Input Parsing
2+
13
In this section, we are going to learn how inputs are parsed in the Ellar route handle functions.
24

35
To get started, we need to create another module for this tutorial.

ellar/core/exceptions/interfaces.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init_subclass__(cls, **kwargs: t.Any) -> None:
3636

3737
class IExceptionMiddlewareService:
3838
@abstractmethod
39-
def build_exception_handlers(self) -> None:
39+
def build_exception_handlers(self, *exception_handlers: IExceptionHandler) -> None:
4040
pass
4141

4242
@abstractmethod

ellar/core/exceptions/service.py

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

33
from ellar.di import injectable
44

5-
from ..conf.config import Config
65
from .handlers import (
76
APIExceptionHandler,
87
HTTPExceptionHandler,
@@ -21,14 +20,13 @@ class ExceptionMiddlewareService(IExceptionMiddlewareService):
2120
RequestValidationErrorHandler(),
2221
]
2322

24-
def __init__(self, config: Config) -> None:
25-
self.config = config
23+
def __init__(self) -> None:
2624
self._status_handlers: t.Dict[int, IExceptionHandler] = {}
2725
self._exception_handlers: t.Dict[t.Type[Exception], IExceptionHandler] = {}
2826
self._500_error_handler: t.Optional[IExceptionHandler] = None
2927

30-
def build_exception_handlers(self) -> None:
31-
handlers = list(self.DEFAULTS) + list(self.config.EXCEPTION_HANDLERS)
28+
def build_exception_handlers(self, *exception_handlers: IExceptionHandler) -> None:
29+
handlers = list(self.DEFAULTS) + list(exception_handlers)
3230
for key, value in handlers:
3331
if key == 500:
3432
self._500_error_handler = value

ellar/core/main.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ def __init__(
5757
), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."
5858

5959
self._config = config
60-
# TODO: read auto_bind from configure
6160
self._injector: EllarInjector = injector
6261

6362
self._global_guards = [] if global_guards is None else list(global_guards)
63+
self._exception_handlers = list(self.config.EXCEPTION_HANDLERS)
64+
6465
self._user_middleware = list(t.cast(list, self.config.MIDDLEWARE))
6566

6667
self.on_startup = RouterEventManager(
@@ -81,8 +82,12 @@ def __init__(
8182
self.router = ApplicationRouter(
8283
routes=self._get_module_routes(),
8384
redirect_slashes=self.config.REDIRECT_SLASHES,
84-
on_startup=[self.on_startup.async_run],
85-
on_shutdown=[self.on_shutdown.async_run],
85+
on_startup=[self.on_startup.async_run]
86+
if self.config.DEFAULT_LIFESPAN_HANDLER is None
87+
else None,
88+
on_shutdown=[self.on_shutdown.async_run]
89+
if self.config.DEFAULT_LIFESPAN_HANDLER is None
90+
else None,
8691
default=self.config.DEFAULT_NOT_FOUND_HANDLER, # type: ignore
8792
lifespan=self.config.DEFAULT_LIFESPAN_HANDLER,
8893
)
@@ -179,8 +184,12 @@ def build_middleware_stack(self) -> ASGIApp:
179184
service_middleware = self.injector.get(
180185
IExceptionMiddlewareService # type:ignore
181186
)
182-
service_middleware.build_exception_handlers()
187+
service_middleware.build_exception_handlers(*self._exception_handlers)
183188
error_handler = service_middleware.get_500_error_handler()
189+
allowed_hosts = self.config.ALLOWED_HOSTS
190+
191+
if self.debug and allowed_hosts != ["*"]:
192+
allowed_hosts = ["*"]
184193

185194
middleware = (
186195
[
@@ -196,7 +205,7 @@ def build_middleware_stack(self) -> ASGIApp:
196205
),
197206
Middleware(
198207
TrustedHostMiddleware,
199-
allowed_hosts=self.config.ALLOWED_HOSTS,
208+
allowed_hosts=allowed_hosts,
200209
www_redirect=self.config.REDIRECT_HOST,
201210
),
202211
Middleware(
@@ -271,8 +280,8 @@ def add_exception_handler(
271280
) -> None:
272281
_added_any = False
273282
for exception_handler in exception_handlers:
274-
if exception_handler not in self.config.EXCEPTION_HANDLERS:
275-
self.config.EXCEPTION_HANDLERS.append(exception_handler)
283+
if exception_handler not in self._exception_handlers:
284+
self._exception_handlers.append(exception_handler)
276285
_added_any = True
277286
if _added_any:
278287
self.rebuild_middleware_stack()

ellar/core/templating/interface.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,14 @@ class AppTemplating(JinjaTemplating):
135135
_static_app: t.Optional[ASGIApp]
136136
_injector: "EllarInjector"
137137
has_static_files: bool
138-
build_middleware_stack: t.Callable
138+
139+
@abstractmethod
140+
def build_middleware_stack(self) -> t.Callable: # pragma: no cover
141+
pass
142+
143+
@abstractmethod
144+
def rebuild_middleware_stack(self) -> None: # pragma: no cover
145+
pass
139146

140147
def get_module_loaders(self) -> t.Generator[ModuleTemplating, None, None]:
141148
for loader in self._injector.get_templating_modules().values():
@@ -149,7 +156,8 @@ def debug(self) -> bool:
149156
def debug(self, value: bool) -> None:
150157
del self.__dict__["jinja_environment"]
151158
self.config.DEBUG = value
152-
self.build_middleware_stack()
159+
# TODO: Add warning
160+
self.rebuild_middleware_stack()
153161

154162
@cached_property
155163
def jinja_environment(self) -> BaseEnvironment:

ellar/core/versioning/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ def get_version_resolver(self, scope: TScope) -> BaseAPIVersioningResolver:
3434

3535

3636
class DefaultAPIVersioning(BaseAPIVersioning):
37-
pass
37+
"""
38+
A PlaceHolder Versioning Scheme.
39+
"""
3840

3941

4042
class UrlPathAPIVersioning(BaseAPIVersioning):

0 commit comments

Comments
 (0)