Skip to content

Commit c95c1eb

Browse files
committed
Merge branch 'dev'
2 parents 2feab4d + 6aeb432 commit c95c1eb

21 files changed

+421
-439
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,4 @@ ignored/
165165
*.db
166166
*.log
167167
translatedocs.py
168+
*/*.log

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "echonext_di"]
2+
path = echonext_di
3+
url = [email protected]:alexeev-prog/echonext_di.git

Doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ PROJECT_NAME = "pyEchoNext"
4848
# could be handy for archiving the generated documentation or if some version
4949
# control system is used.
5050

51-
PROJECT_NUMBER = "0.7.12"
51+
PROJECT_NUMBER = "0.7.13"
5252

5353
# Using the PROJECT_BRIEF tag one can provide an optional one line description
5454
# for a project that appears at the top of each page and should give viewer a

README.md

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Welcome to **EchoNext**, where innovation meets simplicity! Are you tired of the
4545

4646
**Imagine** a lightweight framework that empowers you to create modern web applications with lightning speed and flexibility. With EchoNext, you're not just coding; you're building a masterpiece!
4747

48-
> Last stable version: 0.7.12 alpha
48+
> Last stable version: 0.7.13 alpha
4949
5050
> Next Big Update: ASYNC & unicorn support
5151
@@ -85,7 +85,7 @@ Welcome to **EchoNext**, where innovation meets simplicity! Are you tired of the
8585
| Asynchronous Capabilities | COMING SOON || ✔️ || ✔️ |
8686
| Performance | 🔥 High | 🐢 Moderate | 🚀 Very High | 🐢 Moderate | 🚀 Very High |
8787
| Framework Weight | ✔️ | ✔️ | ✔️ | ❌ Heavy | ✔️ |
88-
| Ecosystem | 🛠️ Modular | 🎨 Flexible | 🎨 Modular | ⚙️ Monolithic | ⚙️ Modular |
88+
| Ecosystem | 🛠️ Flexible | 🎨 Flexible | 🎨 Modular | ⚙️ Monolithic | ⚙️ Modular |
8989
| Ease of Use | ✔️ | ✔️ | ✔️ || ✔️ |
9090
| Configurability | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
9191
| Documentation Quality | 📚 Excellent | 📚 Good | 📚 Excellent | 📚 Very Good | 📚 Good |
@@ -94,7 +94,7 @@ Welcome to **EchoNext**, where innovation meets simplicity! Are you tired of the
9494
| Community Size | 📢 Growing | 📢 Large | 📢 Growing | 📢 Large | 📢 Emerging |
9595
| Built-in Template Engine | ✔️ Jinja2 & builtin | ✔️ Jinja2 | ✔️ Jinja2 | ✔️ Django | ✔️ Jinja2 |
9696
| Task Queue Integration || ✔️ Celery | ✔️ Celery | ✔️ Celery | ✔️ Celery |
97-
| Static File Serving | 🌍 Manual | 🌍 Manual | 🚀 Built-in | 🚀 Built-in | 🚀 Built-in |
97+
| Static File Serving | 🚀 Built-in | 🌍 Manual | 🚀 Built-in | 🚀 Built-in | 🚀 Built-in |
9898
| Analytics Integration | ✔️ Easy | 🛠️ Manual | ✔️ Easy || ✔️ Easy |
9999

100100
📈 Note: Echonext excels in performance while staying lightweight, making it a top-notch choice for your next project!
@@ -139,9 +139,95 @@ Once installed, you can start using the library in your Python projects. Check o
139139

140140
<p align="right">(<a href="#readme-top">back to top</a>)</p>
141141

142+
## ⚙️ Depends Injection
143+
pyEchoNext is universal, and you are free to use any Dependency-Injection framework. But we recommend using the specially developed [echonextdi](https://github.com/alexeev-prog/echonext_di). It is simple and fast to use.
144+
145+
> echonext_di goes in echonext dependencies
146+
147+
```python
148+
from echonextdi.containers.container import Container
149+
from echonextdi.depends import Depends
150+
from echonextdi.providers.callable_provider import CallableProvider
151+
152+
153+
def sqrt(a: int, b: int = 2):
154+
return a**b
155+
156+
157+
class SQRT_Dependency:
158+
def __init__(self, sqrt):
159+
self.sqrt = sqrt
160+
161+
162+
container = Container()
163+
container.register("sqrt", CallableProvider(sqrt))
164+
165+
166+
def calculate(number: int, depend: Depends = Depends(container, SQRT_Dependency)):
167+
print(f"{number} ^2 = {depend().sqrt(2)}")
168+
169+
170+
calculate(4) # Output: 16
171+
```
172+
142173
## 💻 Usage Examples
143174
You can view examples at [examples directory](./examples).
144175

176+
### Basic With Depends Injection
177+
178+
```python
179+
import os
180+
181+
from pyechonext.app import ApplicationType, EchoNext
182+
from pyechonext.config import Settings
183+
from pyechonext.middleware import middlewares
184+
from pyechonext.mvc.controllers import PageController
185+
from pyechonext.urls import URL
186+
187+
from echonextdi.containers.container import Container
188+
from echonextdi.depends import Depends
189+
from echonextdi.providers.callable_provider import CallableProvider
190+
191+
192+
class IndexController(PageController):
193+
def get(self, request, response, **kwargs):
194+
return "Hello"
195+
196+
def post(self, request, response, **kwargs):
197+
return "Hello"
198+
199+
200+
def say_hello(name: str, phrase: str = 'Hello'):
201+
return f'{phrase} {name}'
202+
203+
204+
class Hello_Dependency:
205+
def __init__(self, say_hello):
206+
self.say_hello = say_hello
207+
208+
209+
container = Container()
210+
container.register("say_hello", CallableProvider(say_hello))
211+
212+
url_patterns = [URL(path="/", controller=IndexController)]
213+
settings = Settings(
214+
BASE_DIR=os.path.dirname(os.path.abspath(__file__)), TEMPLATES_DIR="templates"
215+
)
216+
echonext = EchoNext(
217+
__name__,
218+
settings,
219+
middlewares,
220+
urls=url_patterns,
221+
application_type=ApplicationType.HTML,
222+
)
223+
224+
225+
@echonext.route_page("/hello/{name}")
226+
def hello(request, response, name: str = "World", depend: Depends = Depends(container, Hello_Dependency)):
227+
response.body = depend().say_hello(name)
228+
229+
```
230+
145231
### Performance caching
146232

147233
```python
@@ -594,7 +680,7 @@ To test the web framework, PyTest with the pytest-cov plugin is used. You can lo
594680

595681
| Statements | Miss | Coverage |
596682
|------------|------------|----------|
597-
| 1327 | 936 | 34% |
683+
| 1553 | 720 | 54% |
598684

599685
## Documentation 🌍
600686
Extended documentation and framework specifications are available at the following links:

SECURITY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ currently being supported with security updates.
77

88
| Version | Supported |
99
| ------- | ------------------ |
10+
| 0.7.13 | :white_check_mark: |
1011
| 0.7.12 | :white_check_mark: |
1112
| 0.7.11 | :white_check_mark: |
1213
| 0.6.11 | :white_check_mark: |

echonext_di

Submodule echonext_di added at 73473b8

examples/modern.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import os
2+
3+
from pyechonext.app import ApplicationType, EchoNext
4+
from pyechonext.config import Settings
5+
from pyechonext.middleware import middlewares
6+
from pyechonext.mvc.controllers import PageController
7+
from pyechonext.urls import URL
8+
9+
from echonextdi.containers.container import Container
10+
from echonextdi.depends import Depends
11+
from echonextdi.providers.callable_provider import CallableProvider
12+
13+
14+
class IndexController(PageController):
15+
def get(self, request, response, **kwargs):
16+
return "Hello"
17+
18+
def post(self, request, response, **kwargs):
19+
return "Hello"
20+
21+
22+
def say_hello(name: str, phrase: str = 'Hello'):
23+
return f'{phrase} {name}'
24+
25+
26+
class Hello_Dependency:
27+
def __init__(self, say_hello):
28+
self.say_hello = say_hello
29+
30+
31+
container = Container()
32+
container.register("say_hello", CallableProvider(say_hello))
33+
34+
url_patterns = [URL(path="/", controller=IndexController)]
35+
settings = Settings(
36+
BASE_DIR=os.path.dirname(os.path.abspath(__file__)), TEMPLATES_DIR="templates"
37+
)
38+
echonext = EchoNext(
39+
__name__,
40+
settings,
41+
middlewares,
42+
urls=url_patterns,
43+
application_type=ApplicationType.HTML,
44+
)
45+
46+
47+
@echonext.route_page("/hello/{name}")
48+
def hello(request, response, name: str = "World", depend: Depends = Depends(container, Hello_Dependency)):
49+
response.body = depend().say_hello(name)

pyechonext/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from rich import print
2323
from rich.traceback import install
2424

25-
__version__ = "0.7.12"
25+
__version__ = "0.7.13"
2626
install(show_locals=True)
2727

2828

pyechonext/app.py

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
from typing import Any, Callable, Iterable, List, Optional, Tuple, Type
55

66
from loguru import logger
7+
from requests import Session as RequestsSession
78
from socks import method
9+
from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
810

911
from pyechonext.cache import InMemoryCache
1012
from pyechonext.config import Settings
1113
from pyechonext.i18n_l10n import JSONi18nLoader, JSONLocalizationLoader
1214
from pyechonext.logging import setup_logger
1315
from pyechonext.middleware import BaseMiddleware
1416
from pyechonext.mvc.controllers import PageController
15-
from pyechonext.mvc.routes import Router, RoutesTypes
17+
from pyechonext.mvc.routes import Router, RoutesTypes, Route
1618
from pyechonext.request import Request
1719
from pyechonext.response import Response
1820
from pyechonext.static import StaticFile, StaticFilesManager
@@ -115,6 +117,11 @@ def __init__(
115117

116118
logger.debug(f"Application {self.application_type.value}: {self.app_name}")
117119

120+
def test_session(self, host: str = "echonext"):
121+
session = RequestsSession()
122+
session.mount(prefix=f"http://{host}", adapter=RequestsWSGIAdapter(self))
123+
return session
124+
118125
def _get_request(self, environ: dict) -> Request:
119126
"""
120127
Gets the request.
@@ -136,6 +143,20 @@ def _get_response(self, request: Request) -> Response:
136143
"""
137144
return Response(request, content_type=self.application_type.value)
138145

146+
def add_route(self, page_path: str, handler: Callable):
147+
"""
148+
Adds a route.
149+
150+
:param page_path: The page path
151+
:type page_path: str
152+
:param handler: The handler
153+
:type handler: Callable
154+
"""
155+
if inspect.isclass(handler):
156+
self.router.add_url(URL(path=page_path, controller=handler))
157+
else:
158+
self.router.add_page_route(page_path, handler)
159+
139160
def route_page(self, page_path: str) -> Callable:
140161
"""
141162
Creating a New Page Route
@@ -284,15 +305,59 @@ def _serve_static_file(
284305
)
285306
return response
286307

287-
def _handle_request(self, request: Request) -> Response:
308+
def _check_handler(self, request: Request, handler: Callable) -> Callable:
288309
"""
289-
Handle response from request
310+
Check and return handler
311+
312+
:param request: The request
313+
:type request: Request
314+
:param handler: The handler
315+
:type handler: Callable
316+
317+
:returns: handler
318+
:rtype: Callable
319+
320+
:raises MethodNotAllow: request method not allowed
321+
"""
322+
if isinstance(handler, PageController) or inspect.isclass(handler):
323+
handler = getattr(handler, request.method.lower(), None)
290324

291-
:param request: The request
292-
:type request: Request
325+
if handler is None:
326+
raise MethodNotAllow(
327+
f'Method "{request.method.lower()}" don\'t allowed: {request.path}'
328+
)
293329

294-
:returns: Response callable object
295-
:rtype: Response
330+
return handler
331+
332+
def _filling_response(self, route: Route, response: Response, request: Request, result: Any, handler: Callable):
333+
"""
334+
Filling response
335+
336+
:param route: The route
337+
:type route: Route
338+
:param response: The response
339+
:type response: Response
340+
:param handler: The handler
341+
:type handler: Callable
342+
"""
343+
if route.route_type == RoutesTypes.URL_BASED:
344+
view = route.handler.get_rendered_view(request, result, self)
345+
response.body = view
346+
else:
347+
string = self.i18n_loader.get_string(result)
348+
response.body = self.get_and_save_cache_item(string, string)
349+
350+
def _handle_request(self, request: Request) -> Response:
351+
"""
352+
Handle response from request
353+
354+
:param request: The request
355+
:type request: Request
356+
357+
:returns: Response callable object
358+
:rtype: Response
359+
360+
:raises URLNotFound: 404 Error
296361
"""
297362
logger.debug(f"Handle request: {request.path}")
298363
response = self._get_response(request)
@@ -302,25 +367,16 @@ def _handle_request(self, request: Request) -> Response:
302367
handler = route.handler
303368

304369
if handler is not None:
305-
if isinstance(handler, PageController) or inspect.isclass(handler):
306-
handler = getattr(handler, request.method.lower(), None)
307-
308-
if handler is None:
309-
raise MethodNotAllow(
310-
f'Method "{request.method.lower()}" don\'t allowed: {request.path}'
311-
)
370+
handler = self._check_handler(request, handler)
312371

313372
result = handler(request, response, **kwargs)
314373

315374
if isinstance(result, Response):
316375
result = result.body
376+
elif result is None:
377+
return response
317378

318-
if route.route_type == RoutesTypes.URL_BASED:
319-
view = route.handler.get_rendered_view(request, result, self)
320-
response.body = view
321-
else:
322-
string = self.i18n_loader.get_string(result)
323-
response.body = self.get_and_save_cache_item(string, string)
379+
self._filling_response(route, response, request, result, handler)
324380
else:
325381
raise URLNotFound(f'URL "{request.path}" not found.')
326382

pyechonext/depends/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)