Skip to content

Commit 38de88c

Browse files
authored
Merge pull request #117 from eadwinCode/style_and_body_fixes
MyPy Liniting Fix And Embedded Body
2 parents 6e08b27 + 14b0231 commit 38de88c

27 files changed

+169
-142
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ clean: ## Removing cached python compiled files
99
find . -name \*pyo | xargs rm -fv
1010
find . -name \*~ | xargs rm -fv
1111
find . -name __pycache__ | xargs rm -rfv
12+
ruff --clean
1213

1314
install: ## Install dependencies
1415
flit install --deps develop --symlink
@@ -22,7 +23,7 @@ lint: ## Run code linters
2223
ruff check ellar tests examples
2324
mypy ellar
2425

25-
fmt format: ## Run code formatters
26+
fmt format:clean ## Run code formatters
2627
black ellar tests examples
2728
ruff check --fix ellar tests examples
2829

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ from ellar.common import Module, exception_handler, JSONResponse, Response, IHos
235235
from ellar.core import ModuleBase
236236

237237
from ellar.samples.modules import HomeModule
238-
from .apps.car.module import CarModule
238+
from .car.module import CarModule
239239

240240

241241
@Module(modules=[HomeModule, CarModule])

docs/cli/command-grouping.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ For instance, a `db` command may have sub-commands like `makemigrations`, `migra
66
To achieve this use-case, let us create a file `commands.py` in the root level of the project.
77

88
```python
9-
from ellar.commands import EllarTyper
9+
from ellar.common import EllarTyper
1010

1111
db = EllarTyper(name="db")
1212

docs/cli/create-module-command.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ This command helps you create an Ellar project module, like a small app within a
33
It depends on the existence of an Ellar project.
44

55
```shell
6-
ellar create-module my_project_module
6+
ellar create-module my_project_module directory
7+
```
8+
for example:
9+
```shell
10+
ellar create-module my_project_module apps/
711
```
8-
912
will create a folder as follows:
1013
```angular2html
1114
john_doe/
@@ -29,3 +32,7 @@ john_doe/
2932
├─ server.py
3033
3134
```
35+
36+
## **New Command CLI Arguments**
37+
- `module-name` Set the resulting module name.
38+
- `directory` Path to dump the scaffolded files. `.` can be used to select current directory.

docs/cli/create-project-command.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
This command helps you create just an Ellar project provided the `"pyproject.toml"` file exists in the working directory(`CWD`)
33

44
```shell
5-
ellar create-project my_new_project
5+
ellar create-project my_new_project directory
6+
```
7+
8+
for example:
9+
```shell
10+
ellar create-project my_new_project
611
```
712

813
will create a folder as follows:
@@ -17,3 +22,8 @@ my_new_project/
1722
├─ server.py
1823
├─ __init__.py
1924
```
25+
26+
27+
## **Create Project Command Arguments**
28+
- `project-name` Set the resulting project module name.
29+
- `directory` Path to dump the scaffolded files. `.` can be used to select current directory.

docs/cli/new-command.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ my-project/
2828
If you want to name your project differently than the folder, you can pass the `--project-name` option.
2929

3030
```shell
31-
ellar new my-project --project-name john-doe
31+
ellar new my-project path/to/scaffold-the-new-project
3232
```
3333
will create a folder as follows:
3434
```angular2html
35-
my-project/
36-
├─ john_doe/
35+
path/to/scaffold-the-new-project/
36+
├─ my_project/
3737
│ ├─ apps/
3838
│ │ ├─ __init__.py
3939
│ ├─ core/
@@ -49,5 +49,6 @@ my-project/
4949
5050
```
5151

52-
## **New Command CLI Options**
53-
- `--project-name` Set the resulting project module name. Defaults to folder-name is not provided.
52+
## **New Command CLI Arguments**
53+
- `project-name` Set the resulting project module name.
54+
- `directory` Path to dump the scaffolded files. `.` can be used to select current directory.

docs/overview/step-one.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ Also, some boilerplate files are populated and installed in a new `project_name`
2424
```shell
2525
project-name/
2626
├─ project_name/
27-
│ ├─ apps/
28-
│ │ ├─ __init__.py
2927
│ ├─ core/
3028
│ ├─ domain/
3129
│ ├─ config.py
@@ -44,7 +42,6 @@ A brief overview of generated core files:
4442
|----------------------------|----------------------------------------------------------------------------------------------------------------|
4543
| `pyproject.toml` | Python project metadata store. |
4644
| `README.md` | Project Description and documentation. |
47-
| `project_name.apps` | Root path to all project modules. |
4845
| `project_name.core` | Core/business logic folder. |
4946
| `project_name.domain` | Domain logic folder. |
5047
| `project_name.config` | Application configuration file |
@@ -124,7 +121,7 @@ One last thing, before we move to the next page, we need to create an app `modul
124121
125122
Lets add a `car` module/app to our project:
126123
```shell
127-
$(venv) ellar create-module car
124+
$(venv) ellar create-module car apps/
128125
```
129126
The result of this CLI command is stored in `project-name/project_name/apps`
130127
```

ellar/auth/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from .decorators import CheckPolicies
2-
from .guard import AuthorizationGuard
1+
from .decorators import Authorize, CheckPolicies
32
from .handlers import BaseAuthenticationHandler
43
from .identity import UserIdentity
4+
from .interceptor import AuthorizationInterceptor
55
from .interfaces import IIdentitySchemes
66
from .policy import (
77
BasePolicyHandler,
@@ -13,7 +13,8 @@
1313

1414
__all__ = [
1515
"CheckPolicies",
16-
"AuthorizationGuard",
16+
"AuthorizationInterceptor",
17+
"Authorize",
1718
"BaseAuthenticationHandler",
1819
"CheckPolicies",
1920
"BasePolicyHandler",

ellar/auth/decorators.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import typing as t
22

33
from ellar.common import set_metadata as set_meta
4+
from ellar.common.constants import ROUTE_INTERCEPTORS
45

56
from .constants import POLICY_KEYS
7+
from .interceptor import AuthorizationInterceptor
68
from .policy import PolicyType
79

810

911
def CheckPolicies(*policies: t.Union[str, PolicyType]) -> t.Callable:
1012
"""
1113
========= CONTROLLER AND ROUTE FUNCTION DECORATOR ==============
12-
14+
Decorates a controller or a route function with specific policy requirements
1315
:param policies:
1416
:return:
1517
"""
@@ -19,3 +21,13 @@ def _decorator(target: t.Callable) -> t.Union[t.Callable, t.Any]:
1921
return target
2022

2123
return _decorator
24+
25+
26+
def Authorize() -> t.Callable:
27+
"""
28+
========= CONTROLLER AND ROUTE FUNCTION DECORATOR ==============
29+
Decorates a controller class or route function with `AuthorizationInterceptor`
30+
:return:
31+
"""
32+
33+
return set_meta(ROUTE_INTERCEPTORS, [AuthorizationInterceptor])

ellar/auth/guard.py renamed to ellar/auth/interceptor.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typing as t
22
from functools import partial
33

4-
from ellar.common import APIException, GuardCanActivate, IExecutionContext
4+
from ellar.common import APIException, EllarInterceptor, IExecutionContext
55
from ellar.di import injectable
66
from starlette import status
77

@@ -10,7 +10,7 @@
1010

1111

1212
@injectable
13-
class AuthorizationGuard(GuardCanActivate):
13+
class AuthorizationInterceptor(EllarInterceptor):
1414
status_code = status.HTTP_403_FORBIDDEN
1515
exception_class = APIException
1616

@@ -31,14 +31,16 @@ def _get_policy_instance(
3131
return context.get_service_provider().get(policy)
3232
return policy
3333

34-
async def can_activate(self, context: IExecutionContext) -> bool:
34+
async def intercept(
35+
self, context: IExecutionContext, next_interceptor: t.Callable[..., t.Coroutine]
36+
) -> t.Any:
3537
if not context.user.is_authenticated:
3638
raise self.exception_class(status_code=401)
3739

3840
policies = self.get_route_handler_policy(context)
3941

4042
if not policies:
41-
return True
43+
return await next_interceptor()
4244

4345
partial_get_policy_instance = partial(self._get_policy_instance, context)
4446

@@ -48,7 +50,7 @@ async def can_activate(self, context: IExecutionContext) -> bool:
4850
if not result:
4951
self.raise_exception()
5052

51-
return True
53+
return await next_interceptor()
5254

5355
def raise_exception(self) -> None:
5456
raise self.exception_class(status_code=self.status_code)

ellar/cache/backends/redis/backend.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
from ellar.common.helper.event_loop import get_or_create_eventloop
66

77
try:
8-
from redis.asyncio import Redis # type: ignore
9-
from redis.asyncio.connection import ConnectionPool # type: ignore
8+
from redis.asyncio import Redis
9+
from redis.asyncio.connection import ConnectionPool
1010
except ImportError as e: # pragma: no cover
1111
raise RuntimeError(
1212
"To use `RedisCacheBackend`, you have to install 'redis' package e.g. `pip install redis`"
1313
) from e
14+
15+
1416
from ...interface import IBaseCacheBackendAsync
1517
from ...make_key_decorator import make_key_decorator, make_key_decorator_and_validate
1618
from ...model import BaseCacheBackend
@@ -61,7 +63,7 @@ def decr(self, key: str, delta: int = 1, version: t.Optional[str] = None) -> int
6163

6264

6365
class RedisCacheBackend(_RedisCacheBackendSync, BaseCacheBackend):
64-
MEMCACHE_CLIENT: t.Any = Redis
66+
MEMCACHE_CLIENT: t.Type[Redis] = Redis
6567
"""Redis-based cache backend.
6668
6769
Redis Server Construct example::
@@ -145,7 +147,11 @@ async def set_async(
145147
if ttl == 0:
146148
await client.delete(key)
147149

148-
return bool(await client.set(key, value, ex=self.get_backend_ttl(ttl)))
150+
return bool(
151+
await client.set(
152+
key, value, ex=self.get_backend_ttl(ttl) # type:ignore[arg-type]
153+
)
154+
)
149155

150156
@make_key_decorator
151157
async def delete_async(self, key: str, version: t.Optional[str] = None) -> bool:
@@ -164,7 +170,10 @@ async def touch_async(
164170
if ttl is None:
165171
res = await client.persist(key)
166172
return bool(res)
167-
res = await client.expire(key, self.get_backend_ttl(ttl))
173+
174+
res = await client.expire(
175+
key, self.get_backend_ttl(ttl) # type:ignore[arg-type]
176+
)
168177
return bool(res)
169178

170179
@make_key_decorator

ellar/common/decorators/controller.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,12 @@ def reflect_all_controller_type_routes(cls: t.Type[ControllerBase]) -> None:
7070
)
7171

7272

73-
@t.overload
74-
def Controller(
75-
prefix: t.Optional[str] = None,
76-
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]: # pragma: no cover
77-
...
78-
79-
80-
@t.overload
8173
def Controller(
8274
prefix: t.Optional[str] = None,
8375
*,
8476
name: t.Optional[str] = None,
8577
include_in_schema: bool = True,
86-
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]: # pragma: no cover
78+
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]:
8779
"""
8880
========= CLASS DECORATOR ==============
8981
@@ -93,22 +85,6 @@ def Controller(
9385
:param include_in_schema: include controller in OPENAPI schema
9486
:return: t.Type[ControllerBase]
9587
"""
96-
...
97-
98-
99-
def Controller(
100-
prefix: t.Optional[str] = None,
101-
*,
102-
name: t.Optional[str] = None,
103-
include_in_schema: bool = True,
104-
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]:
105-
"""
106-
Controller Class Decorator
107-
:param prefix: Route Prefix default=[ControllerName]
108-
:param name: route name prefix for url reversing, eg name:route_name default=controller_name
109-
:param include_in_schema: include controller in OPENAPI schema
110-
:return: t.Type[ControllerBase]
111-
"""
11288
_prefix: t.Optional[t.Any] = prefix if prefix is not None else NOT_SET
11389
if prefix and isinstance(prefix, type):
11490
_prefix = NOT_SET

ellar/common/interfaces/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .exceptions import IExceptionHandler, IExceptionMiddlewareService
1313
from .guard_consumer import IGuardsConsumer
1414
from .interceptor_consumer import IInterceptorsConsumer
15+
from .middleware import IEllarMiddleware
1516
from .module import IModuleSetup
1617
from .response_model import IResponseModel
1718
from .templating import IModuleTemplateLoader
@@ -37,4 +38,5 @@
3738
"IApplicationStartup",
3839
"IAPIVersioning",
3940
"IAPIVersioningResolver",
41+
"IEllarMiddleware",
4042
]

ellar/common/interfaces/exceptions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77

88

99
class IExceptionHandler(ABC, t.Iterable):
10+
@classmethod
11+
def __get_validators__(
12+
cls: t.Type["IExceptionHandler"],
13+
) -> t.Iterable[t.Callable[..., t.Any]]:
14+
# for Pydantic Model Validation
15+
yield cls.__validate
16+
17+
@classmethod
18+
def __validate(cls, v: t.Any) -> t.Any:
19+
if not isinstance(v, cls):
20+
raise ValueError(f"Expected IExceptionHandler object, received: {type(v)}")
21+
return v
22+
1023
def __eq__(self, other: t.Any) -> bool:
1124
if isinstance(other, IExceptionHandler):
1225
return other.exception_type_or_code == self.exception_type_or_code

ellar/common/interfaces/middleware.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import typing as t
2+
3+
4+
class IEllarMiddleware:
5+
@classmethod
6+
def __get_validators__(
7+
cls: t.Type["IEllarMiddleware"],
8+
) -> t.Iterable[t.Callable[..., t.Any]]:
9+
# for Pydantic Model Validation
10+
yield cls.__validate
11+
12+
@classmethod
13+
def __validate(cls, v: t.Any) -> t.Any:
14+
if not isinstance(v, cls):
15+
raise ValueError(f"Expected EllarMiddleware object, received: {type(v)}")
16+
return v # pragma: no cover

ellar/common/interfaces/versioning.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,16 @@ class IAPIVersioning(ABC):
1414
@abstractmethod
1515
def get_version_resolver(self, scope: TScope) -> IAPIVersioningResolver:
1616
"""Retrieve Version Resolver"""
17+
18+
@classmethod
19+
def __get_validators__(
20+
cls: t.Type["IAPIVersioning"],
21+
) -> t.Iterable[t.Callable[..., t.Any]]:
22+
# for Pydantic Model Validation
23+
yield cls.__validate
24+
25+
@classmethod
26+
def __validate(cls, v: t.Any) -> t.Any:
27+
if not isinstance(v, cls):
28+
raise ValueError(f"Expected BaseAPIVersioning, received: {type(v)}")
29+
return v

0 commit comments

Comments
 (0)