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.
Files changed (48) hide show
  1. py2http-0.1.61/PKG-INFO +269 -0
  2. {py2http-0.1.59 → py2http-0.1.61}/README.md +17 -8
  3. py2http-0.1.61/py2http/__init__.py +25 -0
  4. {py2http-0.1.59 → py2http-0.1.61}/py2http/bottle_plugins.py +18 -19
  5. {py2http-0.1.59 → py2http-0.1.61}/py2http/config.py +25 -10
  6. py2http-0.1.61/py2http/constants.py +7 -0
  7. {py2http-0.1.59 → py2http-0.1.61}/py2http/decorators.py +75 -70
  8. {py2http-0.1.59 → py2http-0.1.61}/py2http/default_configs.py +44 -30
  9. {py2http-0.1.59 → py2http-0.1.61}/py2http/diagnosis.py +1 -2
  10. {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/example_service.py +2 -2
  11. {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/example_service_bottle.py +5 -5
  12. {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/example_service_flask.py +3 -3
  13. py2http-0.1.61/py2http/examples/object_dispatch.py +98 -0
  14. py2http-0.1.61/py2http/middleware.py +71 -0
  15. {py2http-0.1.59 → py2http-0.1.61}/py2http/openapi_utils.py +88 -75
  16. {py2http-0.1.59 → py2http-0.1.61}/py2http/route_utils.py +16 -4
  17. {py2http-0.1.59 → py2http-0.1.61}/py2http/schema_tools.py +97 -48
  18. {py2http-0.1.59 → py2http-0.1.61}/py2http/service.py +164 -103
  19. py2http-0.1.61/py2http/tests/example_service_test.py +33 -0
  20. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/simple_run_process_test.py +9 -6
  21. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/test_decorators.py +9 -9
  22. py2http-0.1.61/py2http/tests/test_not_set_defaults.py +73 -0
  23. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/test_p2h2p.py +14 -16
  24. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/utils_for_testing.py +4 -4
  25. py2http-0.1.61/py2http/types.py +31 -0
  26. {py2http-0.1.59 → py2http-0.1.61}/py2http/util.py +55 -39
  27. py2http-0.1.61/py2http.egg-info/PKG-INFO +269 -0
  28. {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/SOURCES.txt +2 -0
  29. {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/requires.txt +4 -0
  30. {py2http-0.1.59 → py2http-0.1.61}/setup.cfg +6 -2
  31. py2http-0.1.59/PKG-INFO +0 -246
  32. py2http-0.1.59/py2http/__init__.py +0 -17
  33. py2http-0.1.59/py2http/constants.py +0 -5
  34. py2http-0.1.59/py2http/middleware.py +0 -51
  35. py2http-0.1.59/py2http/tests/example_service_test.py +0 -33
  36. py2http-0.1.59/py2http/types.py +0 -14
  37. py2http-0.1.59/py2http.egg-info/PKG-INFO +0 -246
  38. {py2http-0.1.59 → py2http-0.1.61}/LICENSE +0 -0
  39. {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/__init__.py +0 -0
  40. {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/custom_input_mapper.py +0 -0
  41. {py2http-0.1.59 → py2http-0.1.61}/py2http/examples/custom_input_mappers.py +0 -0
  42. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/__init__.py +0 -0
  43. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/objects_for_testing.py +0 -0
  44. {py2http-0.1.59 → py2http-0.1.61}/py2http/tests/test_flask.py +0 -0
  45. {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/dependency_links.txt +0 -0
  46. {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/not-zip-safe +0 -0
  47. {py2http-0.1.59 → py2http-0.1.61}/py2http.egg-info/top_level.txt +0 -0
  48. {py2http-0.1.59 → py2http-0.1.61}/setup.py +0 -0
@@ -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 = divident
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 = '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 ['HS256']
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('Authorization', '')
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={'verify_signature': self._verify},
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
- 'error': f'Invalid authentication token "{token}", {str(error)}'
71
+ "error": f'Invalid authentication token "{token}", {str(error)}'
73
72
  }
74
73
  )
75
- warn(f'Invalid JWT: {token}')
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('Authorization', '')
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({'error': 'invalid API key'})
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['Access-Control-Allow-Origin'] = self._origins
106
- response.headers[
107
- 'Access-Control-Allow-Methods'
108
- ] = 'GET, POST, PUT, DELETE, OPTIONS'
109
- response.headers[
110
- 'Access-Control-Allow-Headers'
111
- ] = 'Origin, Accept, Content-Type, X-Requested-With, Authorization, X-api-key'
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 = 'aiohttp'
4
- BOTTLE = 'bottle'
5
- FLASK = '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
- """ this is meant to allow nested configs by function name
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 '$else' in result:
31
- result = result['$else']
32
- elif options.get('type', None) is not dict:
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('funcname', getattr(func, '__name__', None))
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('type', None) # align names expected_type <-> type
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'Config {key} does not match type {expected_type}.'
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"