py2http 0.1.60__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 (42) hide show
  1. {py2http-0.1.60 → py2http-0.1.61}/PKG-INFO +16 -4
  2. {py2http-0.1.60 → py2http-0.1.61}/py2http/__init__.py +6 -6
  3. {py2http-0.1.60 → py2http-0.1.61}/py2http/bottle_plugins.py +18 -19
  4. {py2http-0.1.60 → py2http-0.1.61}/py2http/config.py +20 -19
  5. {py2http-0.1.60 → py2http-0.1.61}/py2http/constants.py +6 -5
  6. {py2http-0.1.60 → py2http-0.1.61}/py2http/decorators.py +69 -67
  7. {py2http-0.1.60 → py2http-0.1.61}/py2http/default_configs.py +38 -38
  8. {py2http-0.1.60 → py2http-0.1.61}/py2http/diagnosis.py +1 -2
  9. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/example_service.py +2 -2
  10. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/example_service_bottle.py +5 -5
  11. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/example_service_flask.py +3 -3
  12. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/object_dispatch.py +13 -10
  13. {py2http-0.1.60 → py2http-0.1.61}/py2http/middleware.py +21 -20
  14. {py2http-0.1.60 → py2http-0.1.61}/py2http/openapi_utils.py +82 -82
  15. {py2http-0.1.60 → py2http-0.1.61}/py2http/route_utils.py +11 -10
  16. {py2http-0.1.60 → py2http-0.1.61}/py2http/schema_tools.py +96 -54
  17. {py2http-0.1.60 → py2http-0.1.61}/py2http/service.py +112 -95
  18. py2http-0.1.61/py2http/tests/example_service_test.py +33 -0
  19. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/simple_run_process_test.py +9 -6
  20. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/test_decorators.py +9 -9
  21. py2http-0.1.61/py2http/tests/test_not_set_defaults.py +73 -0
  22. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/test_p2h2p.py +14 -15
  23. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/utils_for_testing.py +4 -4
  24. {py2http-0.1.60 → py2http-0.1.61}/py2http/types.py +6 -4
  25. {py2http-0.1.60 → py2http-0.1.61}/py2http/util.py +45 -39
  26. {py2http-0.1.60 → py2http-0.1.61}/py2http.egg-info/PKG-INFO +16 -4
  27. {py2http-0.1.60 → py2http-0.1.61}/py2http.egg-info/SOURCES.txt +1 -0
  28. {py2http-0.1.60 → py2http-0.1.61}/setup.cfg +1 -2
  29. py2http-0.1.60/py2http/tests/example_service_test.py +0 -33
  30. {py2http-0.1.60 → py2http-0.1.61}/LICENSE +0 -0
  31. {py2http-0.1.60 → py2http-0.1.61}/README.md +0 -0
  32. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/__init__.py +0 -0
  33. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/custom_input_mapper.py +0 -0
  34. {py2http-0.1.60 → py2http-0.1.61}/py2http/examples/custom_input_mappers.py +0 -0
  35. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/__init__.py +0 -0
  36. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/objects_for_testing.py +0 -0
  37. {py2http-0.1.60 → py2http-0.1.61}/py2http/tests/test_flask.py +0 -0
  38. {py2http-0.1.60 → py2http-0.1.61}/py2http.egg-info/dependency_links.txt +0 -0
  39. {py2http-0.1.60 → py2http-0.1.61}/py2http.egg-info/not-zip-safe +0 -0
  40. {py2http-0.1.60 → py2http-0.1.61}/py2http.egg-info/requires.txt +0 -0
  41. {py2http-0.1.60 → py2http-0.1.61}/py2http.egg-info/top_level.txt +0 -0
  42. {py2http-0.1.60 → py2http-0.1.61}/setup.py +0 -0
@@ -1,15 +1,27 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: py2http
3
- Version: 0.1.60
3
+ Version: 0.1.61
4
4
  Summary: Declarative HTTP service entry point.
5
5
  Home-page: https://github.com/i2mint/py2http
6
- Author: Otosense
7
6
  License: MIT
8
7
  Keywords: http service,web service
9
8
  Platform: any
10
9
  Description-Content-Type: text/markdown
11
- Provides-Extra: testing
12
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
13
25
 
14
26
  [Documentation here](https://i2mint.github.io/py2http/)
15
27
 
@@ -1,9 +1,9 @@
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
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
7
  the functionality of the HTTP services."""
8
8
 
9
9
  from .middleware import mk_jwt_middleware, mk_superadmin_middleware
@@ -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,22 +1,23 @@
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.
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
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.
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
11
 
12
- Overall, the module helps manage and retrieve configuration values for functions,
12
+ Overall, the module helps manage and retrieve configuration values for functions,
13
13
  ensuring they are appropriately set and validated based on the function's requirements.
14
14
  """
15
+
15
16
  from typing import Callable
16
17
 
17
- AIOHTTP = 'aiohttp'
18
- BOTTLE = 'bottle'
19
- FLASK = 'flask'
18
+ AIOHTTP = "aiohttp"
19
+ BOTTLE = "bottle"
20
+ FLASK = "flask"
20
21
 
21
22
 
22
23
  def get_result(configs, func, funcname, key, options):
@@ -41,9 +42,9 @@ def get_result(configs, func, funcname, key, options):
41
42
  dict_value = result.get(funcname, None)
42
43
  if dict_value:
43
44
  result = dict_value
44
- elif '$else' in result:
45
- result = result['$else']
46
- 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:
47
48
  result = None
48
49
  return result
49
50
 
@@ -66,12 +67,12 @@ def mk_config(key, func, configs, defaults, **options):
66
67
  * *type*
67
68
  The expected type of the output (use Callable for functions)
68
69
  """
69
- funcname = options.get('funcname', getattr(func, '__name__', None))
70
+ funcname = options.get("funcname", getattr(func, "__name__", None))
70
71
 
71
72
  result = get_result(configs, func, funcname, key, options)
72
73
 
73
74
  if result:
74
- expected_type = options.get('type', None) # align names expected_type <-> type
75
+ expected_type = options.get("type", None) # align names expected_type <-> type
75
76
  if not expected_type:
76
77
  assert key in defaults, f'Missing default value for key "{key}"'
77
78
  default_value = defaults.get(key)
@@ -81,7 +82,7 @@ def mk_config(key, func, configs, defaults, **options):
81
82
  expected_type = type(default_value)
82
83
  assert expected_type == type(None) or isinstance(
83
84
  result, expected_type
84
- ), f'Config {key} does not match type {expected_type}.'
85
+ ), f"Config {key} does not match type {expected_type}."
85
86
  else:
86
87
  result = defaults.get(key, None)
87
88
  return result
@@ -1,6 +1,7 @@
1
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
- JSON_CONTENT_TYPE = 'application/json'
3
- BINARY_CONTENT_TYPE = 'application/octet-stream'
4
- FORM_CONTENT_TYPE = 'multipart/form-data'
5
- RAW_CONTENT_TYPE = 'text/plain'
6
- HTML_CONTENT_TYPE = 'text/html'
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"
@@ -30,7 +30,11 @@ from i2.signatures import (
30
30
  )
31
31
  from i2.errors import ModuleNotFoundIgnore
32
32
 
33
- from py2http.schema_tools import mk_input_schema_from_func, validate_input
33
+ from py2http.schema_tools import (
34
+ mk_input_schema_from_func,
35
+ param_default,
36
+ validate_input,
37
+ )
34
38
  from py2http.config import AIOHTTP, BOTTLE
35
39
  from py2http.constants import (
36
40
  JSON_CONTENT_TYPE,
@@ -246,7 +250,7 @@ class DecoParam:
246
250
  from typing import Optional
247
251
  import re
248
252
 
249
- token_p = re.compile(r'\w+')
253
+ token_p = re.compile(r"\w+")
250
254
 
251
255
 
252
256
  # TODO: Research how to keep the params in the order they were declared.
@@ -338,7 +342,7 @@ class ParamsSpecifier:
338
342
  def __init__(
339
343
  self,
340
344
  _annotations: Optional[dict] = None,
341
- _names: str = '',
345
+ _names: str = "",
342
346
  _dflt_default=None,
343
347
  _kind=Parameter.KEYWORD_ONLY,
344
348
  **name_and_dflts,
@@ -352,35 +356,35 @@ class ParamsSpecifier:
352
356
  self._dflt_default = _dflt_default
353
357
  self._kind = _kind
354
358
 
355
- if hasattr(self, '__annotations__'):
359
+ if hasattr(self, "__annotations__"):
356
360
  self.__annotations__.update(_annotations)
357
361
  else:
358
362
  self.__annotations__ = _annotations
359
363
 
360
364
  reserved = {
361
- '_annotations',
362
- '_names',
363
- '_dflt_default',
364
- 'from_func',
365
- '_extract_params',
366
- '_to_signature',
367
- '_kind',
368
- 'to_parameter_obj_list',
365
+ "_annotations",
366
+ "_names",
367
+ "_dflt_default",
368
+ "from_func",
369
+ "_extract_params",
370
+ "_to_signature",
371
+ "_kind",
372
+ "to_parameter_obj_list",
369
373
  }
370
374
  _name_and_dflts = {
371
375
  k: v
372
376
  for k, v in self.__class__.__dict__.items()
373
- if not k.startswith('__') and k not in reserved
377
+ if not k.startswith("__") and k not in reserved
374
378
  }
375
379
  _name_and_dflts.update(name_and_dflts or {})
376
380
  self._name_and_dflts = _name_and_dflts
377
381
 
378
- annots = set(getattr(self, '__annotations__', {}))
379
- annots |= set(getattr(self.__class__, '__annotations__', {}))
382
+ annots = set(getattr(self, "__annotations__", {}))
383
+ annots |= set(getattr(self.__class__, "__annotations__", {}))
380
384
 
381
385
  _reserved = reserved.intersection(set(_name_and_dflts) | set(annots))
382
386
  if _reserved:
383
- raise ValueError(f'Sorry, {_reserved} are reserved names')
387
+ raise ValueError(f"Sorry, {_reserved} are reserved names")
384
388
 
385
389
  @classmethod
386
390
  def from_func(cls, func, _dflt_default=None):
@@ -388,23 +392,21 @@ class ParamsSpecifier:
388
392
  _annotations = {
389
393
  x.name: x.annotation for x in params if x.annotation is not Parameter.empty
390
394
  }
391
- _name_and_dflts = dict()
392
- for x in params:
393
- dflt = x.default
394
- if dflt is Parameter.empty:
395
- dflt = _dflt_default
396
- _name_and_dflts.update({x.name: dflt})
397
- _name_and_dflts = {
398
- x.name: x.default if x.default is not Parameter.empty else _dflt_default
399
- for x in params
400
- }
395
+
396
+ def dflt_of(param):
397
+ dflt = param_default(param) # i2's NotSet sentinel counts as "no default"
398
+ return _dflt_default if dflt is Parameter.empty else dflt
399
+
400
+ _name_and_dflts = {x.name: dflt_of(x) for x in params}
401
401
  return cls(
402
- _annotations=_annotations, _dflt_default=_dflt_default, **_name_and_dflts,
402
+ _annotations=_annotations,
403
+ _dflt_default=_dflt_default,
404
+ **_name_and_dflts,
403
405
  )
404
406
 
405
407
  def _extract_params(self):
406
408
  _name_and_dflts = self._name_and_dflts
407
- annots = getattr(self, '__annotations__', {})
409
+ annots = getattr(self, "__annotations__", {})
408
410
  for name in set(annots) - set(_name_and_dflts): # annots_not_in_attrs
409
411
  yield dict(
410
412
  name=name,
@@ -428,7 +430,7 @@ class ParamsSpecifier:
428
430
  return list(self._extract_params())
429
431
 
430
432
  def __repr__(self):
431
- return f'{self()}'
433
+ return f"{self()}"
432
434
 
433
435
 
434
436
  class Decora(Decorator):
@@ -539,7 +541,7 @@ class Decora(Decorator):
539
541
  def __init_subclass__(cls, **kwargs):
540
542
  super().__init_subclass__(**kwargs)
541
543
  if (
542
- '__new__' not in cls.__dict__
544
+ "__new__" not in cls.__dict__
543
545
  ): # if __new__ hasn't been defined in the subclass...
544
546
  params = []
545
547
  # cls_annots = getattr(cls, '__annotations__', {})
@@ -554,8 +556,8 @@ class Decora(Decorator):
554
556
  for p in params:
555
557
  setattr(cls, p.name, p.default)
556
558
  params = [
557
- Parameter('self', PK),
558
- Parameter('func', PK, default=None),
559
+ Parameter("self", PK),
560
+ Parameter("func", PK, default=None),
559
561
  ] + params
560
562
  cls._injected_deco_params = [p.name for p in params]
561
563
 
@@ -564,8 +566,8 @@ class Decora(Decorator):
564
566
  cls._injected_deco_params
565
567
  ):
566
568
  raise TypeError(
567
- 'TypeError: __new__() got unexpected keyword arguments: '
568
- f'{kwargs.keys() - cls._injected_deco_params}'
569
+ "TypeError: __new__() got unexpected keyword arguments: "
570
+ f"{kwargs.keys() - cls._injected_deco_params}"
569
571
  )
570
572
  if func is None:
571
573
  return partial(cls, **kwargs)
@@ -624,7 +626,8 @@ def replace_with_params(target=None, /, *, source=None, inplace=False):
624
626
  target
625
627
  ) # make a copy of the function so we don't
626
628
  target.__signature__ = Signature(
627
- new_params, return_annotation=signature(target).return_annotation,
629
+ new_params,
630
+ return_annotation=signature(target).return_annotation,
628
631
  )
629
632
  return target
630
633
  else:
@@ -694,7 +697,7 @@ def methodizer(func=None, *, instance_params=()):
694
697
  kwargs.update(kwargs_from_self)
695
698
  return func(**kwargs)
696
699
 
697
- set_signature_of_func(method, ['self'] + list(method_argnames))
700
+ set_signature_of_func(method, ["self"] + list(method_argnames))
698
701
 
699
702
  method.__name__ = func.__name__
700
703
 
@@ -706,14 +709,14 @@ from warnings import warn
706
709
 
707
710
  def _handle_exisisting_method_name(cls, method, if_method_exists):
708
711
  if hasattr(cls, method.__name__):
709
- msg = f'{cls} already has a method named {method.__name__}'
710
- if if_method_exists == 'raise':
712
+ msg = f"{cls} already has a method named {method.__name__}"
713
+ if if_method_exists == "raise":
711
714
  raise ValueError(msg)
712
- elif if_method_exists == 'warn':
713
- warn(msg + ' ... Will overwrite anyway.')
714
- elif if_method_exists != 'ignore':
715
+ elif if_method_exists == "warn":
716
+ warn(msg + " ... Will overwrite anyway.")
717
+ elif if_method_exists != "ignore":
715
718
  raise ValueError(
716
- f'if_method_exists value not recognized: {if_method_exists}'
719
+ f"if_method_exists value not recognized: {if_method_exists}"
717
720
  )
718
721
 
719
722
 
@@ -721,7 +724,7 @@ def _handle_exisisting_method_name(cls, method, if_method_exists):
721
724
  # - signatures have different orders every time (need to use ordered containers)
722
725
  # - Values not computed correctly
723
726
  def inject_methodized_funcs(
724
- cls=None, *, funcs=(), instance_params=None, if_method_exists='raise'
727
+ cls=None, *, funcs=(), instance_params=None, if_method_exists="raise"
725
728
  ):
726
729
  """
727
730
 
@@ -762,7 +765,7 @@ def inject_methodized_funcs(
762
765
  # C.g(y, x)
763
766
  # C.h(kwargs, c, x)
764
767
  """
765
- raise NotImplementedError('Not working yet: Come back to it!')
768
+ raise NotImplementedError("Not working yet: Come back to it!")
766
769
  if cls is None:
767
770
  return partial(
768
771
  inject_methodized_funcs,
@@ -787,7 +790,7 @@ def flatten_callables(*callables, func_name=None):
787
790
  """
788
791
  Flatten a pipeline of calls into one function.
789
792
  """
790
- raise NotImplementedError('Meant to be a generalization of mk_flat')
793
+ raise NotImplementedError("Meant to be a generalization of mk_flat")
791
794
  for call in callables:
792
795
  pass
793
796
 
@@ -813,7 +816,7 @@ def _instantiate(cls, *, cache_id, **kwargs):
813
816
 
814
817
  # TODO: signature of flat function doesn't reflect actual call restrictions
815
818
  # TODO: Change default func_name to be dynamically, taking method as default
816
- def mk_flat(cls, method, *, func_name: str = 'flat_func', cls_cache_key: str = None):
819
+ def mk_flat(cls, method, *, func_name: str = "flat_func", cls_cache_key: str = None):
817
820
  """
818
821
  Flatten a simple cls->instance->method call pipeline into one function.
819
822
 
@@ -889,7 +892,7 @@ def mk_flat(cls, method, *, func_name: str = 'flat_func', cls_cache_key: str = N
889
892
  if cls_cache_key:
890
893
  param = dict(name=cls_cache_key, kind=KO, default=None)
891
894
  sig_flat = sig_flat.add_params([param])
892
- sig_flat = sig_flat.remove_names(['self'])
895
+ sig_flat = sig_flat.remove_names(["self"])
893
896
  sig_flat = sig_flat.replace(return_annotation=sig_method.return_annotation)
894
897
 
895
898
  def flat_func(**kwargs):
@@ -950,7 +953,7 @@ def flatten_methods(methods: dict, decorator=None, validate_name_unicity=True):
950
953
  if validate_name_unicity:
951
954
  nb_function_names = len({x.__name__: x for x in functions})
952
955
  if nb_function_names != len(functions):
953
- raise ValueError(f'Some function names are duplicated in {methods}')
956
+ raise ValueError(f"Some function names are duplicated in {methods}")
954
957
  return functions
955
958
 
956
959
 
@@ -987,10 +990,10 @@ def add_attrs(**attrs):
987
990
  return add_attrs_to_func
988
991
 
989
992
 
990
- http_get = add_attrs(http_method='get')
991
- http_post = add_attrs(http_method='post')
992
- http_put = add_attrs(http_method='put')
993
- http_delete = add_attrs(http_method='delete')
993
+ http_get = add_attrs(http_method="get")
994
+ http_post = add_attrs(http_method="post")
995
+ http_put = add_attrs(http_method="put")
996
+ http_delete = add_attrs(http_method="delete")
994
997
 
995
998
 
996
999
  def route(route_name):
@@ -998,7 +1001,7 @@ def route(route_name):
998
1001
 
999
1002
 
1000
1003
  def _validate_and_invoke_mapper(func, inputs):
1001
- request_schema = getattr(func, 'request_schema', None)
1004
+ request_schema = getattr(func, "request_schema", None)
1002
1005
  if request_schema:
1003
1006
  validate_input(inputs, request_schema)
1004
1007
  return func(**inputs)
@@ -1012,10 +1015,8 @@ def _handle_req(func, content_type):
1012
1015
  @wraps(func)
1013
1016
  def input_mapper(req):
1014
1017
  if content_type not in req.content_type:
1015
- raise RuntimeError(
1016
- f"The incoming request's content is of type \
1017
- {req.content_type}, when {content_type} is expected."
1018
- )
1018
+ raise RuntimeError(f"The incoming request's content is of type \
1019
+ {req.content_type}, when {content_type} is expected.")
1019
1020
  inputs = _get_inputs_from_request(req, content_type)
1020
1021
  return _validate_and_invoke_mapper(func, inputs)
1021
1022
 
@@ -1082,7 +1083,8 @@ class ProposalJsonRespEncoder(JSONEncoder):
1082
1083
  # Note: Subclass and replace _pre_process_obj to get different preprocessing
1083
1084
  # Note: To get control from init, definte init to set _pre_process_obj
1084
1085
  _pre_process_obj = partial(
1085
- _json_reponse_preproc, serializer_for_type=_mk_default_serializer_for_type(),
1086
+ _json_reponse_preproc,
1087
+ serializer_for_type=_mk_default_serializer_for_type(),
1086
1088
  )
1087
1089
 
1088
1090
  def default(self, o):
@@ -1091,7 +1093,7 @@ class ProposalJsonRespEncoder(JSONEncoder):
1091
1093
 
1092
1094
 
1093
1095
  def send_json_resp(func):
1094
- framework = os.getenv('PY2HTTP_FRAMEWORK', BOTTLE)
1096
+ framework = os.getenv("PY2HTTP_FRAMEWORK", BOTTLE)
1095
1097
  if framework == AIOHTTP:
1096
1098
 
1097
1099
  async def output_mapper(output, **input_kwargs):
@@ -1118,7 +1120,7 @@ def send_binary_resp(func):
1118
1120
  return pickle.dumps(mapped_output)
1119
1121
 
1120
1122
  output_mapper.content_type = BINARY_CONTENT_TYPE
1121
- output_mapper.response_schema = {'type': 'binary'}
1123
+ output_mapper.response_schema = {"type": "binary"}
1122
1124
  return output_mapper
1123
1125
 
1124
1126
 
@@ -1169,25 +1171,25 @@ def mk_input_mapper(input_map):
1169
1171
 
1170
1172
 
1171
1173
  def _get_inputs_from_request(request, content_type):
1172
- defaults = getattr(request, 'defaults', {})
1173
- if request.method == 'POST':
1174
+ defaults = getattr(request, "defaults", {})
1175
+ if request.method == "POST":
1174
1176
  if content_type == JSON_CONTENT_TYPE:
1175
1177
  inputs = request.json
1176
1178
  elif content_type == RAW_CONTENT_TYPE:
1177
- data = request.body.read().decode('utf-8')
1179
+ data = request.body.read().decode("utf-8")
1178
1180
  inputs = json.loads(data)
1179
1181
  elif content_type == BINARY_CONTENT_TYPE:
1180
1182
  data = request.body.read()
1181
1183
  inputs = pickle.loads(data)
1182
1184
  elif content_type == FORM_CONTENT_TYPE:
1183
1185
  fields = json.loads(
1184
- request.files.pop('__fields').file.read().decode('utf-8')
1186
+ request.files.pop("__fields").file.read().decode("utf-8")
1185
1187
  )
1186
1188
  binaries = {k: v.file.read() for k, v in request.files.items()}
1187
1189
  inputs = dict(fields, **binaries)
1188
1190
  return dict(defaults, **inputs)
1189
1191
  else:
1190
- raise NotImplementedError('Only POST is supported for now')
1192
+ raise NotImplementedError("Only POST is supported for now")
1191
1193
 
1192
1194
 
1193
1195
  def mk_handlers(methods: Iterable, *, decorator=None, cls_cache_key=None):
@@ -1200,7 +1202,7 @@ def mk_handlers(methods: Iterable, *, decorator=None, cls_cache_key=None):
1200
1202
  if inspect.isfunction(meth):
1201
1203
  cls = getattr(
1202
1204
  inspect.getmodule(meth),
1203
- meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
1205
+ meth.__qualname__.split(".<locals>", 1)[0].rsplit(".", 1)[0],
1204
1206
  None,
1205
1207
  )
1206
1208
  if isinstance(cls, type):
@@ -1215,7 +1217,7 @@ def mk_handlers(methods: Iterable, *, decorator=None, cls_cache_key=None):
1215
1217
  handlers = []
1216
1218
  for item in methods:
1217
1219
  has_mappers = isinstance(item, dict)
1218
- method = item['endpoint'] if has_mappers else item
1220
+ method = item["endpoint"] if has_mappers else item
1219
1221
  cls = get_class_that_defined_method(method)
1220
1222
  endpoint = decorator(
1221
1223
  mk_flat(cls, method, func_name=method.__name__, cls_cache_key=cls_cache_key)