py2http 0.1.59__tar.gz → 0.1.61__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.
- py2http-0.1.61/PKG-INFO +269 -0
- {py2http-0.1.59 → py2http-0.1.61}/README.md +17 -8
- py2http-0.1.61/py2http/__init__.py +25 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/bottle_plugins.py +18 -19
- {py2http-0.1.59 → py2http-0.1.61}/py2http/config.py +25 -10
- py2http-0.1.61/py2http/constants.py +7 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/decorators.py +75 -70
- {py2http-0.1.59 → py2http-0.1.61}/py2http/default_configs.py +44 -30
- {py2http-0.1.59 → py2http-0.1.61}/py2http/diagnosis.py +1 -2
- {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/example_service.py +2 -2
- {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/example_service_bottle.py +5 -5
- {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/example_service_flask.py +3 -3
- py2http-0.1.61/py2http/examples/object_dispatch.py +98 -0
- py2http-0.1.61/py2http/middleware.py +71 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/openapi_utils.py +88 -75
- {py2http-0.1.59 → py2http-0.1.61}/py2http/route_utils.py +16 -4
- {py2http-0.1.59 → py2http-0.1.61}/py2http/schema_tools.py +97 -48
- {py2http-0.1.59 → py2http-0.1.61}/py2http/service.py +164 -103
- py2http-0.1.61/py2http/tests/example_service_test.py +33 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/simple_run_process_test.py +9 -6
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/test_decorators.py +9 -9
- py2http-0.1.61/py2http/tests/test_not_set_defaults.py +73 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/test_p2h2p.py +14 -16
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/utils_for_testing.py +4 -4
- py2http-0.1.61/py2http/types.py +31 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/util.py +55 -39
- py2http-0.1.61/py2http.egg-info/PKG-INFO +269 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/SOURCES.txt +2 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/requires.txt +4 -0
- {py2http-0.1.59 → py2http-0.1.61}/setup.cfg +6 -2
- py2http-0.1.59/PKG-INFO +0 -246
- py2http-0.1.59/py2http/__init__.py +0 -17
- py2http-0.1.59/py2http/constants.py +0 -5
- py2http-0.1.59/py2http/middleware.py +0 -51
- py2http-0.1.59/py2http/tests/example_service_test.py +0 -33
- py2http-0.1.59/py2http/types.py +0 -14
- py2http-0.1.59/py2http.egg-info/PKG-INFO +0 -246
- {py2http-0.1.59 → py2http-0.1.61}/LICENSE +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/__init__.py +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/custom_input_mapper.py +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/custom_input_mappers.py +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/__init__.py +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/objects_for_testing.py +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/test_flask.py +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/dependency_links.txt +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/not-zip-safe +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/top_level.txt +0 -0
- {py2http-0.1.59 → py2http-0.1.61}/setup.py +0 -0
py2http-0.1.61/PKG-INFO
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py2http
|
|
3
|
+
Version: 0.1.61
|
|
4
|
+
Summary: Declarative HTTP service entry point.
|
|
5
|
+
Home-page: https://github.com/i2mint/py2http
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: http service,web service
|
|
8
|
+
Platform: any
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: bottle
|
|
12
|
+
Requires-Dist: PyJWT[crypto]
|
|
13
|
+
Requires-Dist: aiohttp
|
|
14
|
+
Requires-Dist: i2
|
|
15
|
+
Requires-Dist: glom
|
|
16
|
+
Requires-Dist: gunicorn
|
|
17
|
+
Requires-Dist: requests
|
|
18
|
+
Requires-Dist: strand
|
|
19
|
+
Requires-Dist: swagger-ui-py
|
|
20
|
+
Requires-Dist: pycparser
|
|
21
|
+
Requires-Dist: http2py
|
|
22
|
+
Provides-Extra: testing
|
|
23
|
+
Requires-Dist: http2py; extra == "testing"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
[Documentation here](https://i2mint.github.io/py2http/)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# py2http
|
|
30
|
+
Dispatching Python functions as http services.
|
|
31
|
+
|
|
32
|
+
You have some python objects and want to get an "equivalent" http service from them...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Run a basic HTTP service from a list of functions.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from py2http.service import run_app
|
|
41
|
+
|
|
42
|
+
# Define or import functions
|
|
43
|
+
def add(a, b):
|
|
44
|
+
return a + b
|
|
45
|
+
|
|
46
|
+
def multiply(a, b):
|
|
47
|
+
return a * b
|
|
48
|
+
|
|
49
|
+
class Divider:
|
|
50
|
+
def __init__(self, dividend):
|
|
51
|
+
self.dividend = dividend
|
|
52
|
+
|
|
53
|
+
def divide(self, divisor):
|
|
54
|
+
return self.dividend / divisor
|
|
55
|
+
|
|
56
|
+
divider_from_ten = Divider(10)
|
|
57
|
+
|
|
58
|
+
# Make a list of functions or instance methods
|
|
59
|
+
func_list = [add, multiply, divider_from_ten.divide]
|
|
60
|
+
|
|
61
|
+
# Create an HTTP server
|
|
62
|
+
run_app(func_list, publish_openapi=True, publish_swagger=True)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The HTTP server will listen on port 3030 by default.
|
|
66
|
+
|
|
67
|
+
Given we asked for those options, you can:
|
|
68
|
+
* access the OpenAPI specification of the http-service at http://localhost:3030/openapi
|
|
69
|
+
* access the swagger docs and fiddle of the http-service at http://localhost:3030/swagger
|
|
70
|
+
* actually use the http-service's routes, for example:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Test the server in a separate process
|
|
74
|
+
import requests
|
|
75
|
+
|
|
76
|
+
url = 'http://localhost:3030/add'
|
|
77
|
+
add_args = {'a': 20, 'b': 22}
|
|
78
|
+
requests.post(url, json=add_args).json()
|
|
79
|
+
# should return 42
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
`run_app` accept many configuration values to customize the handling of HTTP requests and responses. Configuration documentation is listed in the file `config.yaml`.
|
|
85
|
+
|
|
86
|
+
## Method transformation
|
|
87
|
+
|
|
88
|
+
Expose class methods by flattening the init -> method call process.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from py2http import run_app
|
|
92
|
+
from py2http.decorators import mk_flat
|
|
93
|
+
|
|
94
|
+
class Adder:
|
|
95
|
+
def __init__(self, a):
|
|
96
|
+
self.a = a
|
|
97
|
+
|
|
98
|
+
def add(self, b):
|
|
99
|
+
return self.a + b
|
|
100
|
+
|
|
101
|
+
add = mk_flat(Adder, Adder.add, func_name='add')
|
|
102
|
+
|
|
103
|
+
func_list = [add]
|
|
104
|
+
|
|
105
|
+
run_app(func_list)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Input mapping
|
|
109
|
+
|
|
110
|
+
By default, the server will only accept JSON requests and parse the values from the request body as keyword arguments. You can define custom input mappers to perform extra handling on the JSON body or directly on the HTTP library request object, such as default injection or type mapping.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from numpy import array
|
|
114
|
+
import soundfile
|
|
115
|
+
from py2http.decorators import handle_json_req
|
|
116
|
+
|
|
117
|
+
from my_service.data import fetch_user_data
|
|
118
|
+
|
|
119
|
+
@handle_json_req # extracts the JSON body and passes it to the input mapper as a dict
|
|
120
|
+
def array_input_mapper(input_kwargs):
|
|
121
|
+
return {'arr': array(input_kwargs.get('arr', [])),
|
|
122
|
+
'scalar': input_kwargs.get('scalar', 1)}
|
|
123
|
+
|
|
124
|
+
@handle_multipart_req # extracts a multipart body and passes it as a dict
|
|
125
|
+
def sound_file_input_mapper(input_kwargs):
|
|
126
|
+
file_input = input_kwargs['file_upload']
|
|
127
|
+
filename = file_input.filename
|
|
128
|
+
file_bytes = BytesIO(file_input.file)
|
|
129
|
+
wf, sr = soundfile.read(file_bytes)
|
|
130
|
+
return dict(input_kwargs, wf=wf, sr=sr, filename=filename)
|
|
131
|
+
|
|
132
|
+
def custom_header_input_mapper(req): # takes a raw aiohttp request
|
|
133
|
+
user = req.headers.get('User', '')
|
|
134
|
+
return {'user': user}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def array_multiplier(arr, scalar):
|
|
138
|
+
return arr * scalar
|
|
139
|
+
|
|
140
|
+
def save_audio(wf, sr, filename, storage_path='/audio'):
|
|
141
|
+
target = os.path.join(storage_path, filename)
|
|
142
|
+
soundfile.write(target, wf, sr)
|
|
143
|
+
|
|
144
|
+
def get_user_data(user):
|
|
145
|
+
return fetch_user_data(user)
|
|
146
|
+
|
|
147
|
+
array_multiplier.input_mapper = array_input_mapper
|
|
148
|
+
save_audio.input_mapper = sound_file_input_mapper
|
|
149
|
+
get_user_data.input_mapper = custom_header_input_mapper
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Output mapping
|
|
153
|
+
|
|
154
|
+
By default, the server will send the return value of the handler function in an HTTP response in JSON format. You can define custom output mappers to perform extra handling or type conversion.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from io import BytesIO
|
|
158
|
+
import soundfile
|
|
159
|
+
|
|
160
|
+
from py2http.decorators import send_json_resp, send_html_resp
|
|
161
|
+
|
|
162
|
+
from my_service.data import fetch_blog
|
|
163
|
+
|
|
164
|
+
@send_json_resp # sends a JSON response
|
|
165
|
+
def array_output_mapper(output, input_kwargs):
|
|
166
|
+
return {'array_value': output['array_value'].tolist()}
|
|
167
|
+
|
|
168
|
+
@send_html_resp # sends an HTML response
|
|
169
|
+
def mk_html_list(output, input_kwargs):
|
|
170
|
+
tag = input_kwargs.get('tag', 'p')
|
|
171
|
+
item_list = output.split('\n')
|
|
172
|
+
result = [f'<{tag}>{item}</{tag}>' for item in item_list]
|
|
173
|
+
return ''.join(result)
|
|
174
|
+
|
|
175
|
+
def return_wav(output, input_kwargs):
|
|
176
|
+
wf, sr = output
|
|
177
|
+
wf_bytes = BytesIO()
|
|
178
|
+
soundfile.write(wf_bytes, wf, sr)
|
|
179
|
+
binary_result = wf_bytes.read()
|
|
180
|
+
return web.Response(body=binary_result, content_type='application/octet-stream')
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def array_multiplier(arr, scalar):
|
|
184
|
+
return arr * scalar
|
|
185
|
+
|
|
186
|
+
def get_blog_posts(page):
|
|
187
|
+
return fetch_blog(page)
|
|
188
|
+
|
|
189
|
+
def download_audio(filename):
|
|
190
|
+
with open(filename) as fp:
|
|
191
|
+
wf, sr = soundfile.read(fp)
|
|
192
|
+
return wf, sr
|
|
193
|
+
|
|
194
|
+
array_multipler.output_mapper = array_output_mapper
|
|
195
|
+
|
|
196
|
+
get_blog_posts.output_mapper = mk_html_list
|
|
197
|
+
|
|
198
|
+
download_audio.output_mapper = return_wav
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Error handling
|
|
204
|
+
|
|
205
|
+
TODO
|
|
206
|
+
|
|
207
|
+
## Client generation
|
|
208
|
+
|
|
209
|
+
Py2http generates an OpenAPI specification for the server before running. You can use this document with any OpenAPI-compatible client tools. To extract the specification, you can generate a server application object before running it.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
import json
|
|
213
|
+
from py2http import mk_app, run_app
|
|
214
|
+
|
|
215
|
+
def add(a, b):
|
|
216
|
+
return a + b
|
|
217
|
+
|
|
218
|
+
func_list = [add]
|
|
219
|
+
|
|
220
|
+
app = mk_app(func_list)
|
|
221
|
+
openapi_spec = app.openapi_spec
|
|
222
|
+
|
|
223
|
+
with open('~/openapi.json', 'w') as fp:
|
|
224
|
+
json.dump(openapi_spec, fp)
|
|
225
|
+
|
|
226
|
+
run_app(app, port=3030)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Configuration
|
|
230
|
+
|
|
231
|
+
Functions that generate an HTTP service, such as `run_app`, accept a number of keyword arguments for configuration. The available configuration arguments are listed below with their defaults.
|
|
232
|
+
|
|
233
|
+
* `framework=py2http.config.BOTTLE` The HTTP framework to use.
|
|
234
|
+
* `input_mapper=py2http.default_configs.default_input_mapper` A function to map an HTTP request to a dict of input arguments.
|
|
235
|
+
* `output_mapper=py2http.default_configs.default_output_mapper` A function to map the output of a function to an HTTP response.
|
|
236
|
+
* `error_handler=py2http.default_configs.default_error_handler` A function to map an exception to an HTTP response.
|
|
237
|
+
* `header_inputs={}` A dict of object properties in JSON schema format describing keys that should be excluded from request body definitions. These inputs are expected to be extracted from the request headers in the input mapper.
|
|
238
|
+
* `host='localhost'` The hostname the server can be reached at, to include in the OpenAPI specification.
|
|
239
|
+
* `port=3030` The TCP port to listen on.
|
|
240
|
+
* `http_method='post'` The default HTTP method for exposing functions, if a function does not specify its method.
|
|
241
|
+
* `openapi={}` A dict with several configuration options specific to the OpenAPI specification, described below.
|
|
242
|
+
* `logger=None` An object that implements a `log` method.
|
|
243
|
+
* `app_name='HTTP Service'` The name of the application (Flask only).
|
|
244
|
+
* `middleware=[]` A list of middlewares to run (aiohttp only).
|
|
245
|
+
* `plugins=[]` A list of plugins to install (bottle only).
|
|
246
|
+
* `enable_cors=False` Whether to enable CORS features.
|
|
247
|
+
* `cors_allowed_origins='*'` If CORS is enabled, the value to pass in the Access-Control-Allow-Origin header.
|
|
248
|
+
* `publish_openapi=False` Whether to add a GET /openapi route to the app that returns the OpenAPI specification.
|
|
249
|
+
* `openapi_insecure=False` Whether to skip middlewares and plugins for the /openapi route (disabling any authentication).
|
|
250
|
+
* `publish_swagger=False` Whether to add a GET route to the app that returns a Swagger HTML interface.
|
|
251
|
+
* `swagger_url='/swagger'` The URL of the swagger route.
|
|
252
|
+
* `swagger_title='Swagger'` The title to display on the Swagger HTML page.
|
|
253
|
+
* `ssl_certfile=None` The path to a valid SSL certificate.
|
|
254
|
+
* `ssl_keyfile=None` The path to a valid SSL certificate's key.
|
|
255
|
+
|
|
256
|
+
OpenAPI configuration:
|
|
257
|
+
|
|
258
|
+
These values will be put in the OpenAPI specification document. All values are optional.
|
|
259
|
+
|
|
260
|
+
* `title` The title of the application.
|
|
261
|
+
* `version` The version number of the application.
|
|
262
|
+
* `auth`: A dict specifying the authentication details to include in the document, to make it easier for clients to automate authentication.
|
|
263
|
+
* `auth['auth_type']` Either 'jwt' or 'api_key'.
|
|
264
|
+
* `auth['login_details']` A dict describing how a client can log in (for jwt authentication only).
|
|
265
|
+
* `auth['login_details']['login_url']` The URL of the login route.
|
|
266
|
+
* `auth['login_details']['refresh_url']`: The URL of the refresh route.
|
|
267
|
+
* `auth['login_details']['login_inputs']`: A list of strings with the keys expected by the login endpoint.
|
|
268
|
+
* `auth['login_details']['refresh_inputs']`: A list of strings with the keys expected by the refresh endpoint.
|
|
269
|
+
* `auth['login_details']['outputs']`: A list of strings with the keys returned by the login/refresh endpoints.,
|
|
@@ -10,7 +10,8 @@ You have some python objects and want to get an "equivalent" http service from t
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
12
|
Run a basic HTTP service from a list of functions.
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
```python
|
|
14
15
|
from py2http.service import run_app
|
|
15
16
|
|
|
16
17
|
# Define or import functions
|
|
@@ -22,7 +23,7 @@ def multiply(a, b):
|
|
|
22
23
|
|
|
23
24
|
class Divider:
|
|
24
25
|
def __init__(self, dividend):
|
|
25
|
-
self.dividend =
|
|
26
|
+
self.dividend = dividend
|
|
26
27
|
|
|
27
28
|
def divide(self, divisor):
|
|
28
29
|
return self.dividend / divisor
|
|
@@ -33,10 +34,17 @@ divider_from_ten = Divider(10)
|
|
|
33
34
|
func_list = [add, multiply, divider_from_ten.divide]
|
|
34
35
|
|
|
35
36
|
# Create an HTTP server
|
|
36
|
-
run_app(func_list)
|
|
37
|
+
run_app(func_list, publish_openapi=True, publish_swagger=True)
|
|
37
38
|
```
|
|
39
|
+
|
|
38
40
|
The HTTP server will listen on port 3030 by default.
|
|
39
|
-
|
|
41
|
+
|
|
42
|
+
Given we asked for those options, you can:
|
|
43
|
+
* access the OpenAPI specification of the http-service at http://localhost:3030/openapi
|
|
44
|
+
* access the swagger docs and fiddle of the http-service at http://localhost:3030/swagger
|
|
45
|
+
* actually use the http-service's routes, for example:
|
|
46
|
+
|
|
47
|
+
```python
|
|
40
48
|
# Test the server in a separate process
|
|
41
49
|
import requests
|
|
42
50
|
|
|
@@ -54,7 +62,7 @@ requests.post(url, json=add_args).json()
|
|
|
54
62
|
|
|
55
63
|
Expose class methods by flattening the init -> method call process.
|
|
56
64
|
|
|
57
|
-
```
|
|
65
|
+
```python
|
|
58
66
|
from py2http import run_app
|
|
59
67
|
from py2http.decorators import mk_flat
|
|
60
68
|
|
|
@@ -76,7 +84,7 @@ run_app(func_list)
|
|
|
76
84
|
|
|
77
85
|
By default, the server will only accept JSON requests and parse the values from the request body as keyword arguments. You can define custom input mappers to perform extra handling on the JSON body or directly on the HTTP library request object, such as default injection or type mapping.
|
|
78
86
|
|
|
79
|
-
```
|
|
87
|
+
```python
|
|
80
88
|
from numpy import array
|
|
81
89
|
import soundfile
|
|
82
90
|
from py2http.decorators import handle_json_req
|
|
@@ -120,7 +128,7 @@ get_user_data.input_mapper = custom_header_input_mapper
|
|
|
120
128
|
|
|
121
129
|
By default, the server will send the return value of the handler function in an HTTP response in JSON format. You can define custom output mappers to perform extra handling or type conversion.
|
|
122
130
|
|
|
123
|
-
```
|
|
131
|
+
```python
|
|
124
132
|
from io import BytesIO
|
|
125
133
|
import soundfile
|
|
126
134
|
|
|
@@ -174,7 +182,8 @@ TODO
|
|
|
174
182
|
## Client generation
|
|
175
183
|
|
|
176
184
|
Py2http generates an OpenAPI specification for the server before running. You can use this document with any OpenAPI-compatible client tools. To extract the specification, you can generate a server application object before running it.
|
|
177
|
-
|
|
185
|
+
|
|
186
|
+
```python
|
|
178
187
|
import json
|
|
179
188
|
from py2http import mk_app, run_app
|
|
180
189
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""This module provides functionality to easily create HTTP services from Python
|
|
2
|
+
functions and class methods. It includes features such as method transformation,
|
|
3
|
+
input mapping, output mapping, error handling, and client generation.
|
|
4
|
+
The module also allows for customization through various configuration options
|
|
5
|
+
and provides the ability to generate OpenAPI specifications for the created HTTP
|
|
6
|
+
services. Additionally, it includes middleware, plugins, and decorators to enhance
|
|
7
|
+
the functionality of the HTTP services."""
|
|
8
|
+
|
|
9
|
+
from .middleware import mk_jwt_middleware, mk_superadmin_middleware
|
|
10
|
+
from .bottle_plugins import JWTPlugin, ApiKeyAuthPlugin
|
|
11
|
+
from .service import (
|
|
12
|
+
mk_app,
|
|
13
|
+
run_app,
|
|
14
|
+
)
|
|
15
|
+
from .decorators import (
|
|
16
|
+
Decorator,
|
|
17
|
+
DecoParam,
|
|
18
|
+
Decora,
|
|
19
|
+
replace_with_params,
|
|
20
|
+
ch_func_to_all_pk,
|
|
21
|
+
mk_flat,
|
|
22
|
+
mk_handlers,
|
|
23
|
+
add_attrs,
|
|
24
|
+
)
|
|
25
|
+
from .openapi_utils import func_to_openapi_spec
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""Plugins for adding middleware functionality to Bottle apps.
|
|
2
|
-
"""
|
|
1
|
+
"""Plugins for adding middleware functionality to Bottle apps."""
|
|
3
2
|
|
|
4
3
|
from bottle import request, response, abort
|
|
5
4
|
from functools import wraps
|
|
@@ -9,7 +8,7 @@ from typing import Iterable
|
|
|
9
8
|
from warnings import warn
|
|
10
9
|
from py2http.constants import JSON_CONTENT_TYPE
|
|
11
10
|
|
|
12
|
-
OPTIONS =
|
|
11
|
+
OPTIONS = "OPTIONS"
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class JWTPlugin:
|
|
@@ -21,7 +20,7 @@ class JWTPlugin:
|
|
|
21
20
|
|
|
22
21
|
def __init__(
|
|
23
22
|
self,
|
|
24
|
-
secret: str =
|
|
23
|
+
secret: str = "",
|
|
25
24
|
verify: bool = True,
|
|
26
25
|
mapper: dict = None,
|
|
27
26
|
ignore_methods: Iterable[str] = None,
|
|
@@ -39,7 +38,7 @@ class JWTPlugin:
|
|
|
39
38
|
self._verify = verify
|
|
40
39
|
self._mapper = mapper if mapper else {}
|
|
41
40
|
self._ignore_methods = ignore_methods if ignore_methods else []
|
|
42
|
-
self._algorithms = algorithms if algorithms else [
|
|
41
|
+
self._algorithms = algorithms if algorithms else ["HS256"]
|
|
43
42
|
|
|
44
43
|
def __call__(self, handler):
|
|
45
44
|
if self._ignore_methods and handler.method_name in self._ignore_methods:
|
|
@@ -49,13 +48,13 @@ class JWTPlugin:
|
|
|
49
48
|
def wrapped_handler(*args, **kwargs):
|
|
50
49
|
if request.method == OPTIONS:
|
|
51
50
|
return handler(*args, *kwargs)
|
|
52
|
-
auth_header = request.headers.get(
|
|
51
|
+
auth_header = request.headers.get("Authorization", "")
|
|
53
52
|
token = auth_header[7:]
|
|
54
53
|
try:
|
|
55
54
|
decoded = jwt.decode(
|
|
56
55
|
token,
|
|
57
56
|
self._secret,
|
|
58
|
-
options={
|
|
57
|
+
options={"verify_signature": self._verify},
|
|
59
58
|
algorithms=self._algorithms,
|
|
60
59
|
)
|
|
61
60
|
for k, v in self._mapper.items():
|
|
@@ -69,10 +68,10 @@ class JWTPlugin:
|
|
|
69
68
|
response.content_type = JSON_CONTENT_TYPE
|
|
70
69
|
return json.dumps(
|
|
71
70
|
{
|
|
72
|
-
|
|
71
|
+
"error": f'Invalid authentication token "{token}", {str(error)}'
|
|
73
72
|
}
|
|
74
73
|
)
|
|
75
|
-
warn(f
|
|
74
|
+
warn(f"Invalid JWT: {token}")
|
|
76
75
|
return handler(*args, **kwargs)
|
|
77
76
|
|
|
78
77
|
return wrapped_handler
|
|
@@ -84,12 +83,12 @@ class ApiKeyAuthPlugin:
|
|
|
84
83
|
|
|
85
84
|
def __call__(self, handler):
|
|
86
85
|
def wrapped_handler(*args, **kwargs):
|
|
87
|
-
auth_header = request.headers.get(
|
|
86
|
+
auth_header = request.headers.get("Authorization", "")
|
|
88
87
|
if auth_header == self._api_key:
|
|
89
88
|
return handler(*args, **kwargs)
|
|
90
89
|
response.status = 401
|
|
91
90
|
response.content_type = JSON_CONTENT_TYPE
|
|
92
|
-
return json.dumps({
|
|
91
|
+
return json.dumps({"error": "invalid API key"})
|
|
93
92
|
|
|
94
93
|
return wrapped_handler
|
|
95
94
|
|
|
@@ -97,18 +96,18 @@ class ApiKeyAuthPlugin:
|
|
|
97
96
|
# from https://stackoverflow.com/questions/17262170/bottle-py-enabling-cors-for-jquery-ajax-requests
|
|
98
97
|
# TODO: accept lists of headers and methods as init args
|
|
99
98
|
class CorsPlugin:
|
|
100
|
-
def __init__(self, origins: str =
|
|
99
|
+
def __init__(self, origins: str = "*"):
|
|
101
100
|
self._origins = origins
|
|
102
101
|
|
|
103
102
|
def __call__(self, handler):
|
|
104
103
|
def wrapped_handler(*args, **kwargs):
|
|
105
|
-
response.headers[
|
|
106
|
-
response.headers[
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
response.headers[
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
response.headers["Access-Control-Allow-Origin"] = self._origins
|
|
105
|
+
response.headers["Access-Control-Allow-Methods"] = (
|
|
106
|
+
"GET, POST, PUT, DELETE, OPTIONS"
|
|
107
|
+
)
|
|
108
|
+
response.headers["Access-Control-Allow-Headers"] = (
|
|
109
|
+
"Origin, Accept, Content-Type, X-Requested-With, Authorization, X-api-key"
|
|
110
|
+
)
|
|
112
111
|
if request.method != OPTIONS:
|
|
113
112
|
return handler(*args, **kwargs)
|
|
114
113
|
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
"""The code module provides functions for retrieving configuration values for a
|
|
2
|
+
function based on a hierarchy of sources, including function properties,
|
|
3
|
+
configuration dictionaries, and default values. The `get_result` function is used to
|
|
4
|
+
allow nested configurations by function name, with an example provided in the docstring
|
|
5
|
+
for illustration.
|
|
6
|
+
|
|
7
|
+
The `mk_config` function is used to get a configuration value for a function by checking
|
|
8
|
+
the function properties, configuration dictionaries, and default values. It also allows
|
|
9
|
+
for specifying the expected type of the output, with options to provide additional
|
|
10
|
+
options such as the function name and expected type.
|
|
11
|
+
|
|
12
|
+
Overall, the module helps manage and retrieve configuration values for functions,
|
|
13
|
+
ensuring they are appropriately set and validated based on the function's requirements.
|
|
14
|
+
"""
|
|
15
|
+
|
|
1
16
|
from typing import Callable
|
|
2
17
|
|
|
3
|
-
AIOHTTP =
|
|
4
|
-
BOTTLE =
|
|
5
|
-
FLASK =
|
|
18
|
+
AIOHTTP = "aiohttp"
|
|
19
|
+
BOTTLE = "bottle"
|
|
20
|
+
FLASK = "flask"
|
|
6
21
|
|
|
7
22
|
|
|
8
23
|
def get_result(configs, func, funcname, key, options):
|
|
9
|
-
"""
|
|
24
|
+
"""this is meant to allow nested configs by function name
|
|
10
25
|
example:
|
|
11
26
|
|
|
12
27
|
# TODO: See mk_config use and make an actual doctest for get_result
|
|
@@ -27,9 +42,9 @@ def get_result(configs, func, funcname, key, options):
|
|
|
27
42
|
dict_value = result.get(funcname, None)
|
|
28
43
|
if dict_value:
|
|
29
44
|
result = dict_value
|
|
30
|
-
elif
|
|
31
|
-
result = result[
|
|
32
|
-
elif options.get(
|
|
45
|
+
elif "$else" in result:
|
|
46
|
+
result = result["$else"]
|
|
47
|
+
elif options.get("type", None) is not dict:
|
|
33
48
|
result = None
|
|
34
49
|
return result
|
|
35
50
|
|
|
@@ -52,12 +67,12 @@ def mk_config(key, func, configs, defaults, **options):
|
|
|
52
67
|
* *type*
|
|
53
68
|
The expected type of the output (use Callable for functions)
|
|
54
69
|
"""
|
|
55
|
-
funcname = options.get(
|
|
70
|
+
funcname = options.get("funcname", getattr(func, "__name__", None))
|
|
56
71
|
|
|
57
72
|
result = get_result(configs, func, funcname, key, options)
|
|
58
73
|
|
|
59
74
|
if result:
|
|
60
|
-
expected_type = options.get(
|
|
75
|
+
expected_type = options.get("type", None) # align names expected_type <-> type
|
|
61
76
|
if not expected_type:
|
|
62
77
|
assert key in defaults, f'Missing default value for key "{key}"'
|
|
63
78
|
default_value = defaults.get(key)
|
|
@@ -67,7 +82,7 @@ def mk_config(key, func, configs, defaults, **options):
|
|
|
67
82
|
expected_type = type(default_value)
|
|
68
83
|
assert expected_type == type(None) or isinstance(
|
|
69
84
|
result, expected_type
|
|
70
|
-
), f
|
|
85
|
+
), f"Config {key} does not match type {expected_type}."
|
|
71
86
|
else:
|
|
72
87
|
result = defaults.get(key, None)
|
|
73
88
|
return result
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""This module provides functionality for dispatching Python functions as HTTP services. It allows users to create a basic HTTP server from a list of functions or instance methods, customize input and output mapping, handle errors, generate client code from an OpenAPI specification, and configure various aspects of the server setup. The module includes features for transforming class methods, mapping input and output data, handling errors, and generating client code. It also supports configuration options for the HTTP framework, input and output mappers, error handlers, openAPI specifications, logging, middleware, plugins, CORS, SSL certificates, and more."""
|
|
2
|
+
|
|
3
|
+
JSON_CONTENT_TYPE = "application/json"
|
|
4
|
+
BINARY_CONTENT_TYPE = "application/octet-stream"
|
|
5
|
+
FORM_CONTENT_TYPE = "multipart/form-data"
|
|
6
|
+
RAW_CONTENT_TYPE = "text/plain"
|
|
7
|
+
HTML_CONTENT_TYPE = "text/html"
|