pyframex7 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyframex7-0.1.0/PKG-INFO +101 -0
- pyframex7-0.1.0/README.md +69 -0
- pyframex7-0.1.0/pyframe/__init__.py +0 -0
- pyframex7-0.1.0/pyframe/app.py +108 -0
- pyframex7-0.1.0/pyframe/middleware.py +30 -0
- pyframex7-0.1.0/pyframe/response.py +31 -0
- pyframex7-0.1.0/pyframex7.egg-info/PKG-INFO +101 -0
- pyframex7-0.1.0/pyframex7.egg-info/SOURCES.txt +12 -0
- pyframex7-0.1.0/pyframex7.egg-info/dependency_links.txt +1 -0
- pyframex7-0.1.0/pyframex7.egg-info/requires.txt +4 -0
- pyframex7-0.1.0/pyframex7.egg-info/top_level.txt +1 -0
- pyframex7-0.1.0/setup.cfg +4 -0
- pyframex7-0.1.0/setup.py +134 -0
- pyframex7-0.1.0/tests/test_app.py +182 -0
pyframex7-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyframex7
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This is simple python web framework which written for learning purposes
|
|
5
|
+
Home-page: https://github.com/Abduqodir7007/custom-framework
|
|
6
|
+
Author: Abduqodir Arifjanov
|
|
7
|
+
Author-email: abdukodirarifzanov@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
+
Requires-Python: >=3.6.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: gunicorn==25.1.0
|
|
18
|
+
Requires-Dist: webob==1.8.9
|
|
19
|
+
Requires-Dist: requests==2.32.5
|
|
20
|
+
Requires-Dist: requests-wsgi-adapter==0.4.1
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
> **PyFrame can be downloaded and installed from [PyPI](https://pypi.org/project/pyframe/).**
|
|
38
|
+
|
|
39
|
+
# PyFrame
|
|
40
|
+
|
|
41
|
+
PyFrame is a custom Python web framework created for learning purposes.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
- Minimal and easy to understand
|
|
45
|
+
- Custom middleware support
|
|
46
|
+
- Simple request/response handling
|
|
47
|
+
- Supports both class-based and function-based handlers
|
|
48
|
+
- Designed for educational use
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
You can install PyFrame locally by cloning this repository and installing it in editable mode:
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
PyFrame supports both function-based and class-based handlers for defining routes.
|
|
56
|
+
|
|
57
|
+
### Function-Based Handler Example
|
|
58
|
+
```python
|
|
59
|
+
from app import App
|
|
60
|
+
from response import Response
|
|
61
|
+
|
|
62
|
+
app = App()
|
|
63
|
+
|
|
64
|
+
@app.route("/")
|
|
65
|
+
def home(request):
|
|
66
|
+
return Response("Hello, World!")
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Class-Based Handler Example
|
|
71
|
+
```python
|
|
72
|
+
from app import App, Handler
|
|
73
|
+
from response import Response
|
|
74
|
+
|
|
75
|
+
app = App()
|
|
76
|
+
|
|
77
|
+
class HelloHandler(Handler):
|
|
78
|
+
def get(self, request):
|
|
79
|
+
return Response("Hello from class-based handler!")
|
|
80
|
+
|
|
81
|
+
app.add_route("/hello", HelloHandler)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Define your routes using the `@app.route` decorator for function-based handlers or `app.add_route` for class-based handlers.
|
|
85
|
+
- Return a `Response` object from your route handlers.
|
|
86
|
+
- Run your application with `app.run()`.
|
|
87
|
+
app.run()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- Define your routes using the `@app.route` decorator.
|
|
91
|
+
- Return a `Response` object from your route handlers.
|
|
92
|
+
- Run your application with `app.run()`.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Use Cases
|
|
96
|
+
- Learning how web frameworks work
|
|
97
|
+
- Experimenting with middleware and routing
|
|
98
|
+
- Building simple web applications for educational purposes
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
This project is for learning and educational purposes only.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
> **PyFrame can be downloaded and installed from [PyPI](https://pypi.org/project/pyframe/).**
|
|
6
|
+
|
|
7
|
+
# PyFrame
|
|
8
|
+
|
|
9
|
+
PyFrame is a custom Python web framework created for learning purposes.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
- Minimal and easy to understand
|
|
13
|
+
- Custom middleware support
|
|
14
|
+
- Simple request/response handling
|
|
15
|
+
- Supports both class-based and function-based handlers
|
|
16
|
+
- Designed for educational use
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
You can install PyFrame locally by cloning this repository and installing it in editable mode:
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
PyFrame supports both function-based and class-based handlers for defining routes.
|
|
24
|
+
|
|
25
|
+
### Function-Based Handler Example
|
|
26
|
+
```python
|
|
27
|
+
from app import App
|
|
28
|
+
from response import Response
|
|
29
|
+
|
|
30
|
+
app = App()
|
|
31
|
+
|
|
32
|
+
@app.route("/")
|
|
33
|
+
def home(request):
|
|
34
|
+
return Response("Hello, World!")
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Class-Based Handler Example
|
|
39
|
+
```python
|
|
40
|
+
from app import App, Handler
|
|
41
|
+
from response import Response
|
|
42
|
+
|
|
43
|
+
app = App()
|
|
44
|
+
|
|
45
|
+
class HelloHandler(Handler):
|
|
46
|
+
def get(self, request):
|
|
47
|
+
return Response("Hello from class-based handler!")
|
|
48
|
+
|
|
49
|
+
app.add_route("/hello", HelloHandler)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Define your routes using the `@app.route` decorator for function-based handlers or `app.add_route` for class-based handlers.
|
|
53
|
+
- Return a `Response` object from your route handlers.
|
|
54
|
+
- Run your application with `app.run()`.
|
|
55
|
+
app.run()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Define your routes using the `@app.route` decorator.
|
|
59
|
+
- Return a `Response` object from your route handlers.
|
|
60
|
+
- Run your application with `app.run()`.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Use Cases
|
|
64
|
+
- Learning how web frameworks work
|
|
65
|
+
- Experimenting with middleware and routing
|
|
66
|
+
- Building simple web applications for educational purposes
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
This project is for learning and educational purposes only.
|
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import requests
|
|
3
|
+
import wsgiadapter
|
|
4
|
+
from parse import parse
|
|
5
|
+
from webob import Request
|
|
6
|
+
from .response import Response
|
|
7
|
+
from .middleware import Middleware
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PyFramework:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.routes = dict()
|
|
14
|
+
self.exception_handler = None
|
|
15
|
+
self.middleware = Middleware(self)
|
|
16
|
+
|
|
17
|
+
def __call__(self, environ, start_response):
|
|
18
|
+
# status = "200 CREATED"
|
|
19
|
+
|
|
20
|
+
# response_header = [("Content-type", "text/plain")]
|
|
21
|
+
# start_response(status, response_header)
|
|
22
|
+
return self.middleware(environ, start_response)
|
|
23
|
+
|
|
24
|
+
def wsgi_app(self, environ, start_response):
|
|
25
|
+
request = Request(environ)
|
|
26
|
+
response = self.handle_request(request)
|
|
27
|
+
return response(environ, start_response)
|
|
28
|
+
|
|
29
|
+
def add_exception_handler(self, handler):
|
|
30
|
+
self.exception_handler = handler
|
|
31
|
+
|
|
32
|
+
def handle_request(self, request):
|
|
33
|
+
response = Response()
|
|
34
|
+
|
|
35
|
+
handler, kwargs, allowed_methods = self.find_handler(request)
|
|
36
|
+
|
|
37
|
+
if inspect.isclass(handler):
|
|
38
|
+
|
|
39
|
+
handler_method = getattr(handler(), request.method.lower(), None)
|
|
40
|
+
|
|
41
|
+
if handler_method is None:
|
|
42
|
+
return self.method_not_allowed_response(request, response)
|
|
43
|
+
try:
|
|
44
|
+
handler_method(request, response, **kwargs)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
if self.exception_handler is not None:
|
|
47
|
+
self.exception_handler(request, response, e)
|
|
48
|
+
else:
|
|
49
|
+
raise e
|
|
50
|
+
elif inspect.isfunction(handler):
|
|
51
|
+
try:
|
|
52
|
+
if request.method.lower() not in allowed_methods:
|
|
53
|
+
return self.method_not_allowed_response(request, response)
|
|
54
|
+
|
|
55
|
+
handler(request, response, **kwargs)
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
if self.exception_handler is not None:
|
|
59
|
+
self.exception_handler(request, response, e)
|
|
60
|
+
else:
|
|
61
|
+
raise e
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
self.default_response(response)
|
|
65
|
+
|
|
66
|
+
return response
|
|
67
|
+
|
|
68
|
+
def find_handler(self, request):
|
|
69
|
+
allowed_method = None
|
|
70
|
+
for path, handler in self.routes.items():
|
|
71
|
+
handler, allowed_method = handler
|
|
72
|
+
|
|
73
|
+
result = parse(path, request.path)
|
|
74
|
+
|
|
75
|
+
if result is not None:
|
|
76
|
+
return handler, result.named, allowed_method
|
|
77
|
+
|
|
78
|
+
return None, None, allowed_method
|
|
79
|
+
|
|
80
|
+
def add_middleware(self, middleware_class):
|
|
81
|
+
self.middleware.add(middleware_class)
|
|
82
|
+
|
|
83
|
+
def default_response(self, response):
|
|
84
|
+
response.status_code = 404
|
|
85
|
+
response.text = "Not found"
|
|
86
|
+
|
|
87
|
+
def method_not_allowed_response(self, request, response):
|
|
88
|
+
response.text = "METHOD NOT ALLOWED"
|
|
89
|
+
response.status_code = 405
|
|
90
|
+
return response
|
|
91
|
+
|
|
92
|
+
def router(self, path: str, allowed_methods: List[str] = ["get"]):
|
|
93
|
+
assert path not in self.routes, f"Path {path} already exists"
|
|
94
|
+
|
|
95
|
+
def wrapper(handler):
|
|
96
|
+
self.routes[path] = (handler, allowed_methods)
|
|
97
|
+
return handler
|
|
98
|
+
|
|
99
|
+
return wrapper
|
|
100
|
+
|
|
101
|
+
def add_router(self, path, handler, allowed_methods: List[str] = ["get"]):
|
|
102
|
+
assert path not in self.routes, f"Path {path} already exists"
|
|
103
|
+
self.routes[path] = (handler, allowed_methods)
|
|
104
|
+
|
|
105
|
+
def test_session(self):
|
|
106
|
+
session = requests.Session()
|
|
107
|
+
session.mount("http://testserver", wsgiadapter.WSGIAdapter(self))
|
|
108
|
+
return session
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from webob import Request, Response
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Middleware:
|
|
6
|
+
def __init__(self, app):
|
|
7
|
+
self.app = app
|
|
8
|
+
|
|
9
|
+
def add(self, middleware_class):
|
|
10
|
+
self.app = middleware_class(self.app)
|
|
11
|
+
|
|
12
|
+
def process_request(self, request):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def process_response(self, request, response):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def handle_request(self, request):
|
|
19
|
+
self.process_request(request)
|
|
20
|
+
response = self.app.handle_request(request)
|
|
21
|
+
self.process_response(request, response)
|
|
22
|
+
|
|
23
|
+
return response
|
|
24
|
+
|
|
25
|
+
def __call__(self, environ, start_response):
|
|
26
|
+
request = Request(environ)
|
|
27
|
+
response = self.app.handle_request(request)
|
|
28
|
+
|
|
29
|
+
return response(environ, start_response)
|
|
30
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from webob import Response as WebobResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Response:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self.json = None
|
|
10
|
+
self.text = None
|
|
11
|
+
self.content_type = None
|
|
12
|
+
self.body = b""
|
|
13
|
+
self.status_code = 200
|
|
14
|
+
|
|
15
|
+
def change_response(self):
|
|
16
|
+
|
|
17
|
+
if self.json is not None:
|
|
18
|
+
self.body = json.dumps(self.json).encode()
|
|
19
|
+
self.content_type = "application/json"
|
|
20
|
+
|
|
21
|
+
if self.text is not None:
|
|
22
|
+
self.body = self.text
|
|
23
|
+
self.content_type = "text/plain"
|
|
24
|
+
|
|
25
|
+
def __call__(self, environ, start_response) -> Any:
|
|
26
|
+
self.change_response()
|
|
27
|
+
response = WebobResponse(
|
|
28
|
+
body=self.body, status=self.status_code, content_type=self.content_type
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return response(environ, start_response)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyframex7
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This is simple python web framework which written for learning purposes
|
|
5
|
+
Home-page: https://github.com/Abduqodir7007/custom-framework
|
|
6
|
+
Author: Abduqodir Arifjanov
|
|
7
|
+
Author-email: abdukodirarifzanov@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
+
Requires-Python: >=3.6.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: gunicorn==25.1.0
|
|
18
|
+
Requires-Dist: webob==1.8.9
|
|
19
|
+
Requires-Dist: requests==2.32.5
|
|
20
|
+
Requires-Dist: requests-wsgi-adapter==0.4.1
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
> **PyFrame can be downloaded and installed from [PyPI](https://pypi.org/project/pyframe/).**
|
|
38
|
+
|
|
39
|
+
# PyFrame
|
|
40
|
+
|
|
41
|
+
PyFrame is a custom Python web framework created for learning purposes.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
- Minimal and easy to understand
|
|
45
|
+
- Custom middleware support
|
|
46
|
+
- Simple request/response handling
|
|
47
|
+
- Supports both class-based and function-based handlers
|
|
48
|
+
- Designed for educational use
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
You can install PyFrame locally by cloning this repository and installing it in editable mode:
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
PyFrame supports both function-based and class-based handlers for defining routes.
|
|
56
|
+
|
|
57
|
+
### Function-Based Handler Example
|
|
58
|
+
```python
|
|
59
|
+
from app import App
|
|
60
|
+
from response import Response
|
|
61
|
+
|
|
62
|
+
app = App()
|
|
63
|
+
|
|
64
|
+
@app.route("/")
|
|
65
|
+
def home(request):
|
|
66
|
+
return Response("Hello, World!")
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Class-Based Handler Example
|
|
71
|
+
```python
|
|
72
|
+
from app import App, Handler
|
|
73
|
+
from response import Response
|
|
74
|
+
|
|
75
|
+
app = App()
|
|
76
|
+
|
|
77
|
+
class HelloHandler(Handler):
|
|
78
|
+
def get(self, request):
|
|
79
|
+
return Response("Hello from class-based handler!")
|
|
80
|
+
|
|
81
|
+
app.add_route("/hello", HelloHandler)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Define your routes using the `@app.route` decorator for function-based handlers or `app.add_route` for class-based handlers.
|
|
85
|
+
- Return a `Response` object from your route handlers.
|
|
86
|
+
- Run your application with `app.run()`.
|
|
87
|
+
app.run()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- Define your routes using the `@app.route` decorator.
|
|
91
|
+
- Return a `Response` object from your route handlers.
|
|
92
|
+
- Run your application with `app.run()`.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Use Cases
|
|
96
|
+
- Learning how web frameworks work
|
|
97
|
+
- Experimenting with middleware and routing
|
|
98
|
+
- Building simple web applications for educational purposes
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
This project is for learning and educational purposes only.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
pyframe/__init__.py
|
|
4
|
+
pyframe/app.py
|
|
5
|
+
pyframe/middleware.py
|
|
6
|
+
pyframe/response.py
|
|
7
|
+
pyframex7.egg-info/PKG-INFO
|
|
8
|
+
pyframex7.egg-info/SOURCES.txt
|
|
9
|
+
pyframex7.egg-info/dependency_links.txt
|
|
10
|
+
pyframex7.egg-info/requires.txt
|
|
11
|
+
pyframex7.egg-info/top_level.txt
|
|
12
|
+
tests/test_app.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyframe
|
pyframex7-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Note: To use the 'upload' functionality of this file, you must:
|
|
5
|
+
# $ pipenv install twine --dev
|
|
6
|
+
|
|
7
|
+
import io
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from shutil import rmtree
|
|
11
|
+
|
|
12
|
+
from setuptools import find_packages, setup, Command
|
|
13
|
+
|
|
14
|
+
# Package meta-data.
|
|
15
|
+
NAME = 'pyframex7'
|
|
16
|
+
DESCRIPTION = 'This is simple python web framework which written for learning purposes'
|
|
17
|
+
URL = 'https://github.com/Abduqodir7007/custom-framework'
|
|
18
|
+
EMAIL = 'abdukodirarifzanov@gmail.com'
|
|
19
|
+
AUTHOR = 'Abduqodir Arifjanov'
|
|
20
|
+
REQUIRES_PYTHON = '>=3.6.0'
|
|
21
|
+
VERSION = '0.1.0'
|
|
22
|
+
|
|
23
|
+
# What packages are required for this module to be executed?
|
|
24
|
+
REQUIRED = [
|
|
25
|
+
'gunicorn==25.1.0',
|
|
26
|
+
'webob==1.8.9',
|
|
27
|
+
'requests==2.32.5',
|
|
28
|
+
'requests-wsgi-adapter==0.4.1',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# What packages are optional?
|
|
32
|
+
EXTRAS = {
|
|
33
|
+
# 'fancy feature': ['django'],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# The rest you shouldn't have to touch too much :)
|
|
37
|
+
# ------------------------------------------------
|
|
38
|
+
# Except, perhaps the License and Trove Classifiers!
|
|
39
|
+
# If you do change the License, remember to change the Trove Classifier for that!
|
|
40
|
+
|
|
41
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
42
|
+
|
|
43
|
+
# Import the README and use it as the long-description.
|
|
44
|
+
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
|
|
45
|
+
try:
|
|
46
|
+
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
|
47
|
+
long_description = '\n' + f.read()
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
long_description = DESCRIPTION
|
|
50
|
+
|
|
51
|
+
# Load the package's __version__.py module as a dictionary.
|
|
52
|
+
about = {}
|
|
53
|
+
if not VERSION:
|
|
54
|
+
project_slug = NAME.lower().replace("-", "_").replace(" ", "_")
|
|
55
|
+
with open(os.path.join(here, project_slug, '__version__.py')) as f:
|
|
56
|
+
exec(f.read(), about)
|
|
57
|
+
else:
|
|
58
|
+
about['__version__'] = VERSION
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class UploadCommand(Command):
|
|
62
|
+
"""Support setup.py upload."""
|
|
63
|
+
|
|
64
|
+
description = 'Build and publish the package.'
|
|
65
|
+
user_options = []
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def status(s):
|
|
69
|
+
"""Prints things in bold."""
|
|
70
|
+
print('\033[1m{0}\033[0m'.format(s))
|
|
71
|
+
|
|
72
|
+
def initialize_options(self):
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def finalize_options(self):
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def run(self):
|
|
79
|
+
try:
|
|
80
|
+
self.status('Removing previous builds…')
|
|
81
|
+
rmtree(os.path.join(here, 'dist'))
|
|
82
|
+
except OSError:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
self.status('Building Source and Wheel (universal) distribution…')
|
|
86
|
+
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
|
|
87
|
+
|
|
88
|
+
self.status('Uploading the package to PyPI via Twine…')
|
|
89
|
+
os.system('twine upload dist/*')
|
|
90
|
+
|
|
91
|
+
self.status('Pushing git tags…')
|
|
92
|
+
os.system('git tag v{0}'.format(about['__version__']))
|
|
93
|
+
os.system('git push --tags')
|
|
94
|
+
|
|
95
|
+
sys.exit()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Where the magic happens:
|
|
99
|
+
setup(
|
|
100
|
+
name=NAME,
|
|
101
|
+
version=about['__version__'],
|
|
102
|
+
description=DESCRIPTION,
|
|
103
|
+
long_description=long_description,
|
|
104
|
+
long_description_content_type='text/markdown',
|
|
105
|
+
author=AUTHOR,
|
|
106
|
+
author_email=EMAIL,
|
|
107
|
+
python_requires=REQUIRES_PYTHON,
|
|
108
|
+
url=URL,
|
|
109
|
+
packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]),
|
|
110
|
+
# If your package is a single module, use this instead of 'packages':
|
|
111
|
+
# py_modules=['mypackage'],
|
|
112
|
+
|
|
113
|
+
# entry_points={
|
|
114
|
+
# 'console_scripts': ['mycli=mymodule:cli'],
|
|
115
|
+
# },
|
|
116
|
+
install_requires=REQUIRED,
|
|
117
|
+
extras_require=EXTRAS,
|
|
118
|
+
include_package_data=True,
|
|
119
|
+
license='MIT',
|
|
120
|
+
classifiers=[
|
|
121
|
+
# Trove classifiers
|
|
122
|
+
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
|
123
|
+
'License :: OSI Approved :: MIT License',
|
|
124
|
+
'Programming Language :: Python',
|
|
125
|
+
'Programming Language :: Python :: 3',
|
|
126
|
+
'Programming Language :: Python :: 3.6',
|
|
127
|
+
'Programming Language :: Python :: Implementation :: CPython',
|
|
128
|
+
'Programming Language :: Python :: Implementation :: PyPy'
|
|
129
|
+
],
|
|
130
|
+
# $ setup.py publish support.
|
|
131
|
+
cmdclass={
|
|
132
|
+
'upload': UploadCommand,
|
|
133
|
+
},
|
|
134
|
+
)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import json
|
|
3
|
+
from pyframe.middleware import Middleware
|
|
4
|
+
from tests.conftest import app, test_client
|
|
5
|
+
from pyframe.app import PyFramework
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestApp:
|
|
9
|
+
def test_route_registration(self, app: PyFramework):
|
|
10
|
+
|
|
11
|
+
@app.router("/home/")
|
|
12
|
+
def home(request, response):
|
|
13
|
+
response.text = "Hello from home"
|
|
14
|
+
|
|
15
|
+
def test_duplicate_route_registration(self, app: PyFramework):
|
|
16
|
+
|
|
17
|
+
@app.router("/home/")
|
|
18
|
+
def home(request, response):
|
|
19
|
+
response.text = "Hello from home"
|
|
20
|
+
|
|
21
|
+
with pytest.raises(Exception):
|
|
22
|
+
|
|
23
|
+
@app.router("/home/")
|
|
24
|
+
def home2(request, response):
|
|
25
|
+
response.text = "Hello from home"
|
|
26
|
+
|
|
27
|
+
def test_client(self, app: PyFramework, test_client):
|
|
28
|
+
|
|
29
|
+
@app.router("/home/")
|
|
30
|
+
def home(request, response):
|
|
31
|
+
response.text = "Hello from home"
|
|
32
|
+
|
|
33
|
+
res = test_client.get("http://testserver/home/")
|
|
34
|
+
|
|
35
|
+
assert res.text == "Hello from home"
|
|
36
|
+
|
|
37
|
+
def test_parametized_routing(self, app: PyFramework, test_client):
|
|
38
|
+
|
|
39
|
+
@app.router("/home/{name}/")
|
|
40
|
+
def home(request, response, name):
|
|
41
|
+
response.text = f"Hello {name}"
|
|
42
|
+
|
|
43
|
+
res = test_client.get("http://testserver/home/john/")
|
|
44
|
+
|
|
45
|
+
assert res.text == "Hello john"
|
|
46
|
+
|
|
47
|
+
def test_wrong_routes(self, test_client):
|
|
48
|
+
|
|
49
|
+
res = test_client.get("http://testserver/home/")
|
|
50
|
+
|
|
51
|
+
assert res.text == "Not found"
|
|
52
|
+
assert res.status_code == 404
|
|
53
|
+
|
|
54
|
+
def test_class_based_get_routing(self, app, test_client):
|
|
55
|
+
|
|
56
|
+
@app.router("/book/")
|
|
57
|
+
class Book:
|
|
58
|
+
|
|
59
|
+
def get(self, request, response):
|
|
60
|
+
response.text = "Getting books"
|
|
61
|
+
|
|
62
|
+
res1 = test_client.get("http://testserver/book/")
|
|
63
|
+
res2 = test_client.post("http://testserver/book/")
|
|
64
|
+
|
|
65
|
+
assert res1.text == "Getting books"
|
|
66
|
+
assert res2.text == "METHOD NOT ALLOWED"
|
|
67
|
+
assert res2.status_code == 405
|
|
68
|
+
|
|
69
|
+
def test_django_like_routing(self, app, test_client):
|
|
70
|
+
|
|
71
|
+
def book(request, response):
|
|
72
|
+
response.text = "Hello from book"
|
|
73
|
+
|
|
74
|
+
app.add_router("/book/", book)
|
|
75
|
+
res = test_client.get("http://testserver/book/")
|
|
76
|
+
|
|
77
|
+
assert res.text == "Hello from book"
|
|
78
|
+
|
|
79
|
+
def test_custom_exception_handler(self, app: PyFramework, test_client):
|
|
80
|
+
|
|
81
|
+
def on_exception(request, response, exp_class):
|
|
82
|
+
response.text = "Something went wrong"
|
|
83
|
+
|
|
84
|
+
app.add_exception_handler(on_exception)
|
|
85
|
+
|
|
86
|
+
@app.router("/exception/")
|
|
87
|
+
def exception(request, response):
|
|
88
|
+
raise AttributeError("some exception")
|
|
89
|
+
|
|
90
|
+
res = test_client.get("http://testserver/exception/")
|
|
91
|
+
|
|
92
|
+
assert res.text == "Something went wrong"
|
|
93
|
+
|
|
94
|
+
def test_custom_exception_handler_witj_class_based_router(
|
|
95
|
+
self, app: PyFramework, test_client
|
|
96
|
+
):
|
|
97
|
+
|
|
98
|
+
def on_exception(request, response, exception):
|
|
99
|
+
response.text = "Exception from class"
|
|
100
|
+
|
|
101
|
+
app.add_exception_handler(on_exception)
|
|
102
|
+
|
|
103
|
+
@app.router("/book/")
|
|
104
|
+
class Book:
|
|
105
|
+
|
|
106
|
+
def get(self, request, response):
|
|
107
|
+
raise AttributeError("Error")
|
|
108
|
+
|
|
109
|
+
res = test_client.get("http://testserver/book/")
|
|
110
|
+
|
|
111
|
+
assert res.text == "Exception from class"
|
|
112
|
+
|
|
113
|
+
def test_middleware_methods_are_called(self, app: PyFramework, test_client):
|
|
114
|
+
is_process_request_called = False
|
|
115
|
+
is_process_response_called = False
|
|
116
|
+
|
|
117
|
+
class SimpleMiddleware(Middleware):
|
|
118
|
+
def __init__(self, app):
|
|
119
|
+
super().__init__(app)
|
|
120
|
+
|
|
121
|
+
def process_request(self, request):
|
|
122
|
+
|
|
123
|
+
nonlocal is_process_request_called
|
|
124
|
+
is_process_request_called = True
|
|
125
|
+
|
|
126
|
+
def process_response(self, request, response):
|
|
127
|
+
nonlocal is_process_response_called
|
|
128
|
+
is_process_response_called = True
|
|
129
|
+
|
|
130
|
+
app.add_middleware(SimpleMiddleware)
|
|
131
|
+
|
|
132
|
+
@app.router("/home/")
|
|
133
|
+
def home(request, response):
|
|
134
|
+
response.text = "Hello from middleware"
|
|
135
|
+
|
|
136
|
+
res = test_client.get("http://testserver/home/")
|
|
137
|
+
|
|
138
|
+
assert is_process_response_called is True
|
|
139
|
+
assert is_process_request_called is True
|
|
140
|
+
|
|
141
|
+
def test_function_based_allowed_method(self, app: PyFramework, test_client):
|
|
142
|
+
|
|
143
|
+
@app.router("/book/", allowed_methods=["get"])
|
|
144
|
+
def book(request, response):
|
|
145
|
+
response.text = "Hello world"
|
|
146
|
+
|
|
147
|
+
res = test_client.get("http://testserver/book/")
|
|
148
|
+
|
|
149
|
+
assert res.text == "Hello world"
|
|
150
|
+
|
|
151
|
+
def test_function_based_allowed_method_with_wrong_method(self, app, test_client):
|
|
152
|
+
|
|
153
|
+
@app.router("/book/", allowed_methods=["post"])
|
|
154
|
+
def book(request, response):
|
|
155
|
+
response.text = "Checking method allowed"
|
|
156
|
+
|
|
157
|
+
res = test_client.get("http://testserver/book/")
|
|
158
|
+
|
|
159
|
+
assert res.text == "METHOD NOT ALLOWED"
|
|
160
|
+
|
|
161
|
+
def test_json_response(self, app, test_client):
|
|
162
|
+
|
|
163
|
+
@app.router("/json")
|
|
164
|
+
def json_handler(request, response):
|
|
165
|
+
|
|
166
|
+
response_data = {"name": "tom", "number": 10}
|
|
167
|
+
|
|
168
|
+
response.body = response_data
|
|
169
|
+
res = test_client.get("http://testserver/json/").json()
|
|
170
|
+
|
|
171
|
+
assert res.headers["Content-Type"] == "application/json"
|
|
172
|
+
assert res.name == "tom"
|
|
173
|
+
|
|
174
|
+
def test_text_response(self, app, test_client):
|
|
175
|
+
|
|
176
|
+
@app.router("/text/")
|
|
177
|
+
def text_handler(request, response):
|
|
178
|
+
response.text = "this is the response"
|
|
179
|
+
|
|
180
|
+
res = test_client.get("http://testserver/text/")
|
|
181
|
+
|
|
182
|
+
assert "text/plain" in res.headers["Content-Type"]
|