bisslog-flask 0.0.2__tar.gz → 0.0.3__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.
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/PKG-INFO +5 -3
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/builder/builder_flask_app_manager.py +25 -7
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/cli/__init__.py +6 -4
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/cli/commands/build.py +2 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/cli/commands/run.py +1 -1
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/bisslog_flask_http_resolver.py +53 -12
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/bisslog_flask_resolver.py +0 -1
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/bisslog_flask_ws_resolver.py +25 -9
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask.egg-info/PKG-INFO +5 -3
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask.egg-info/SOURCES.txt +2 -0
- bisslog_flask-0.0.3/bisslog_flask.egg-info/requires.txt +12 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/pyproject.toml +3 -2
- bisslog_flask-0.0.3/requirements/requirements-async.txt +1 -0
- bisslog_flask-0.0.3/requirements.txt +3 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/builder/test_builder_flask_app_manager.py +18 -12
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/builder/test_builder_full.py +7 -5
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/cli/test_build.py +2 -4
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/cli/test_cli.py +4 -2
- bisslog_flask-0.0.3/tests/unit/test_http_resolver.py +268 -0
- bisslog_flask-0.0.3/tests/unit/test_http_resolver_lambda_fn.py +93 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/test_init_app_manager.py +25 -8
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/test_ws_resolver.py +4 -3
- bisslog_flask-0.0.2/bisslog_flask.egg-info/requires.txt +0 -9
- bisslog_flask-0.0.2/requirements.txt +0 -3
- bisslog_flask-0.0.2/tests/unit/test_http_resolver.py +0 -97
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/.github/workflows/publish-pypi.yml +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/.github/workflows/publish-test-pypi.yml +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/.github/workflows/receive-changes.yml +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/.gitignore +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/LICENSE +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/README.md +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/builder/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/builder/static_python_construct_data.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/cli/commands/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/init_flask_app_manager.py +1 -1
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/socket_helper/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/socket_helper/socket_helper.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask.egg-info/dependency_links.txt +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask.egg-info/entry_points.txt +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask.egg-info/top_level.txt +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/pylintrc +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/requirements/requirements-flask-optional.txt +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/setup.cfg +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/builder/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/builder/test_static_python_res.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/cli/__init__.py +0 -0
- {bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/cli/test_run.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bisslog_flask
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.3
|
4
4
|
Summary: It is an extension of the bisslog library to support processes with flask
|
5
5
|
Author-email: Darwin Stiven Herrera Cartagena <darwinsherrerac@gmail.com>
|
6
6
|
Project-URL: Homepage, https://github.com/darwinhc/bisslog-flask
|
@@ -10,13 +10,15 @@ Classifier: Operating System :: OS Independent
|
|
10
10
|
Requires-Python: >=3.7
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
-
Requires-Dist: bisslog>=0.0.
|
14
|
-
Requires-Dist: bisslog-schema>=0.0.
|
13
|
+
Requires-Dist: bisslog>=0.0.9
|
14
|
+
Requires-Dist: bisslog-schema>=0.0.10
|
15
15
|
Requires-Dist: flask
|
16
16
|
Provides-Extra: websocket
|
17
17
|
Requires-Dist: flask-socketio; extra == "websocket"
|
18
18
|
Provides-Extra: cors
|
19
19
|
Requires-Dist: flask-cors>=6.0.0; extra == "cors"
|
20
|
+
Provides-Extra: async
|
21
|
+
Requires-Dist: Flask[async]>=2.2; extra == "async"
|
20
22
|
Dynamic: license-file
|
21
23
|
|
22
24
|
# bisslog-flask
|
{bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/builder/builder_flask_app_manager.py
RENAMED
@@ -8,6 +8,7 @@ configuration, and runtime setup.
|
|
8
8
|
The generated code is returned as a full Python script and can be written to a file (e.g.,
|
9
9
|
`flask_app.py`).
|
10
10
|
"""
|
11
|
+
|
11
12
|
from typing import Optional, Callable
|
12
13
|
import json
|
13
14
|
|
@@ -17,6 +18,7 @@ from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerWebsocket
|
|
17
18
|
from bisslog_schema.setup import get_setup_metadata
|
18
19
|
from bisslog_schema.use_case_code_inspector.use_case_code_metadata import UseCaseCodeInfo, \
|
19
20
|
UseCaseCodeInfoClass, UseCaseCodeInfoObject
|
21
|
+
|
20
22
|
from .static_python_construct_data import StaticPythonConstructData
|
21
23
|
|
22
24
|
|
@@ -147,6 +149,9 @@ if "JWT_SECRET_KEY" in os.environ:
|
|
147
149
|
StaticPythonConstructData
|
148
150
|
The generated code for the HTTP trigger.
|
149
151
|
"""
|
152
|
+
imports = {
|
153
|
+
"flask": {"jsonify"}
|
154
|
+
}
|
150
155
|
starting_build = ""
|
151
156
|
mapper_code_lines = []
|
152
157
|
if trigger_info.mapper is not None:
|
@@ -155,7 +160,7 @@ if "JWT_SECRET_KEY" in os.environ:
|
|
155
160
|
f"base={json.dumps(trigger_info.mapper)})")
|
156
161
|
mapper_code_lines.append(f"""
|
157
162
|
res_map = {mapper_name}.map({{
|
158
|
-
"path_query":
|
163
|
+
"path_query": route_vars,
|
159
164
|
"body": request.get_json(silent=True) or {{}},
|
160
165
|
"params": request.args.to_dict(),
|
161
166
|
"headers": request.headers,
|
@@ -166,20 +171,33 @@ if "JWT_SECRET_KEY" in os.environ:
|
|
166
171
|
|
167
172
|
lines = [
|
168
173
|
f'@app.route("{flask_path}", methods=["{method}"])',
|
169
|
-
f'def {handler_name}():',
|
170
174
|
]
|
175
|
+
|
176
|
+
if use_case_code_info.is_coroutine:
|
177
|
+
lines.append(f"async def {handler_name}(**route_vars):")
|
178
|
+
else:
|
179
|
+
lines.append(f"def {handler_name}(**route_vars):")
|
180
|
+
|
171
181
|
if not mapper_code_lines:
|
172
182
|
lines.append(" kwargs = {}")
|
173
|
-
lines.append(" kwargs.update(
|
183
|
+
lines.append(" kwargs.update(route_vars)")
|
174
184
|
lines.append(" kwargs.update(request.get_json(silent=True) or {})")
|
175
185
|
lines.append(" kwargs.update(request.args.to_dict())")
|
176
186
|
lines.append(" kwargs.update(dict(request.headers))")
|
177
|
-
|
187
|
+
var_to_unpack = "kwargs"
|
178
188
|
else:
|
179
189
|
lines.extend(mapper_code_lines)
|
180
|
-
|
190
|
+
var_to_unpack = "res_map"
|
191
|
+
|
192
|
+
if use_case_code_info.is_coroutine:
|
193
|
+
lines.append(f' result = await {uc_callable}(**{var_to_unpack})')
|
194
|
+
else:
|
195
|
+
lines.append(f' result = {uc_callable}(**{var_to_unpack})\n')
|
196
|
+
|
197
|
+
lines.append(' return jsonify(result)\n')
|
181
198
|
|
182
|
-
return StaticPythonConstructData(build=starting_build,
|
199
|
+
return StaticPythonConstructData(build=starting_build,
|
200
|
+
body="\n".join(lines), importing=imports)
|
183
201
|
|
184
202
|
@staticmethod
|
185
203
|
def _generate_use_case_code_websocket_trigger(
|
@@ -217,7 +235,7 @@ if "JWT_SECRET_KEY" in os.environ:
|
|
217
235
|
imports = {
|
218
236
|
use_case_code_info.module: {use_case_code_info.name},
|
219
237
|
"flask_sock": {"Sock"},
|
220
|
-
"flask": {"request"},
|
238
|
+
"flask": {"request", "jsonify"},
|
221
239
|
"bisslog.utils.mapping": {"Mapper"},
|
222
240
|
"json": None
|
223
241
|
}
|
@@ -1,10 +1,11 @@
|
|
1
|
+
"""Command-line interface for the `bisslog_flask` package."""
|
2
|
+
import argparse
|
1
3
|
import os
|
2
4
|
import sys
|
3
|
-
import argparse
|
4
5
|
import traceback
|
5
6
|
|
6
|
-
from .commands.run import run
|
7
7
|
from .commands.build import build_boiler_plate_flask
|
8
|
+
from .commands.run import run
|
8
9
|
|
9
10
|
|
10
11
|
def main():
|
@@ -62,7 +63,8 @@ def main():
|
|
62
63
|
build_parser.add_argument("--encoding", type=str, default="utf-8",
|
63
64
|
help="File encoding (default: utf-8).")
|
64
65
|
build_parser.add_argument("--target-filename", type=str, default="flask_app.py",
|
65
|
-
help="Filename to write the generated
|
66
|
+
help="Filename to write the generated "
|
67
|
+
"boilerplate (default: flask_app.py)")
|
66
68
|
|
67
69
|
args = parser.parse_args()
|
68
70
|
|
@@ -82,7 +84,7 @@ def main():
|
|
82
84
|
encoding=args.encoding,
|
83
85
|
target_filename=args.target_filename
|
84
86
|
)
|
85
|
-
except Exception as e:
|
87
|
+
except Exception as e: # pylint: disable=broad-except
|
86
88
|
traceback.print_exc()
|
87
89
|
print(e)
|
88
90
|
sys.exit(1)
|
@@ -10,8 +10,10 @@ The generated code supports HTTP and WebSocket routes, environment-based securit
|
|
10
10
|
configuration, and respects the Bisslog runtime setup defined via decorators.
|
11
11
|
"""
|
12
12
|
from typing import Optional
|
13
|
+
|
13
14
|
from ...builder.builder_flask_app_manager import bisslog_flask_builder
|
14
15
|
|
16
|
+
|
15
17
|
def build_boiler_plate_flask(
|
16
18
|
metadata_file: Optional[str] = None,
|
17
19
|
use_cases_folder_path: Optional[str] = None,
|
@@ -18,7 +18,7 @@ def run(metadata_file: Optional[str] = None,
|
|
18
18
|
secret_key: Optional[str] = None,
|
19
19
|
jwt_secret_key: Optional[str] = None):
|
20
20
|
"""
|
21
|
-
Run a Flask application using metadata and use
|
21
|
+
Run a Flask application using metadata and use-case source.
|
22
22
|
|
23
23
|
This function creates and runs a Flask app configured through the
|
24
24
|
BisslogFlask integration layer. It loads metadata definitions,
|
{bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/bisslog_flask_http_resolver.py
RENAMED
@@ -16,10 +16,12 @@ Dependencies
|
|
16
16
|
- bisslog_schema
|
17
17
|
- bisslog.utils.mapping
|
18
18
|
"""
|
19
|
+
import inspect
|
19
20
|
from copy import deepcopy
|
20
|
-
from typing import Callable, Optional, Dict
|
21
|
+
from typing import Callable, Optional, Dict, Union, Awaitable, Any
|
21
22
|
|
22
23
|
from flask import Flask, request, jsonify
|
24
|
+
|
23
25
|
try:
|
24
26
|
from flask_cors import cross_origin
|
25
27
|
except ImportError:
|
@@ -82,10 +84,10 @@ class BisslogFlaskHttpResolver(BisslogFlaskResolver):
|
|
82
84
|
|
83
85
|
@staticmethod
|
84
86
|
def _use_case_factory(
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
87
|
+
use_case_name: str,
|
88
|
+
fn: Callable,
|
89
|
+
mapper: Optional[Dict[str, str]] = None,
|
90
|
+
trigger: Optional[TriggerHttp] = None
|
89
91
|
):
|
90
92
|
"""
|
91
93
|
Factory to produce a Flask view function with optional mapping and CORS.
|
@@ -109,10 +111,49 @@ class BisslogFlaskHttpResolver(BisslogFlaskResolver):
|
|
109
111
|
use_case_fn_copy = deepcopy(fn)
|
110
112
|
__mapper__ = Mapper(name=f"Mapper {use_case_name}", base=mapper) if mapper else None
|
111
113
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
114
|
+
is_async = (
|
115
|
+
inspect.iscoroutinefunction(fn)
|
116
|
+
or inspect.iscoroutinefunction(getattr(fn, "__call__", None))
|
117
|
+
)
|
118
|
+
|
119
|
+
if is_async:
|
120
|
+
async def uc(*args, **kwargs):
|
121
|
+
if __mapper__ is None:
|
122
|
+
more_kwargs = {}
|
123
|
+
if request.method.lower() != "get":
|
124
|
+
more_kwargs.update(request.get_json(silent=True) or {})
|
125
|
+
res = await use_case_fn_copy(*args, **kwargs, **more_kwargs)
|
126
|
+
return jsonify(res)
|
127
|
+
|
128
|
+
res_map = __mapper__.map({
|
129
|
+
"path_query": request.view_args or {},
|
130
|
+
"body": request.get_json(silent=True) or {},
|
131
|
+
"params": request.args.to_dict(),
|
132
|
+
"headers": request.headers,
|
133
|
+
})
|
134
|
+
res = await use_case_fn_copy(**res_map)
|
135
|
+
return jsonify(res)
|
136
|
+
|
137
|
+
view = uc
|
138
|
+
else:
|
139
|
+
def uc(*args, **kwargs):
|
140
|
+
if __mapper__ is None:
|
141
|
+
more_kwargs = {}
|
142
|
+
if request.method.lower() != "get":
|
143
|
+
more_kwargs.update(request.get_json(silent=True) or {})
|
144
|
+
res = use_case_fn_copy(*args, **kwargs, **more_kwargs)
|
145
|
+
return jsonify(res)
|
146
|
+
|
147
|
+
res_map = __mapper__.map({
|
148
|
+
"path_query": request.view_args or {},
|
149
|
+
"body": request.get_json(silent=True) or {},
|
150
|
+
"params": request.args.to_dict(),
|
151
|
+
"headers": request.headers,
|
152
|
+
})
|
153
|
+
res = use_case_fn_copy(**res_map)
|
154
|
+
return jsonify(res)
|
155
|
+
|
156
|
+
view = uc
|
116
157
|
|
117
158
|
# Apply CORS dynamically if allowed
|
118
159
|
if trigger and trigger.allow_cors:
|
@@ -124,13 +165,13 @@ class BisslogFlaskHttpResolver(BisslogFlaskResolver):
|
|
124
165
|
"allow_headers": ["Content-Type", "Authorization"],
|
125
166
|
"supports_credentials": True
|
126
167
|
}
|
127
|
-
return cross_origin(**cors_kwargs)(
|
168
|
+
return cross_origin(**cors_kwargs)(view)
|
128
169
|
|
129
|
-
return
|
170
|
+
return view
|
130
171
|
|
131
172
|
@classmethod
|
132
173
|
def _add_use_case(cls, app: Flask, use_case_info: UseCaseInfo, trigger: TriggerInfo,
|
133
|
-
use_case_function):
|
174
|
+
use_case_function: Union[Callable[..., Any], Callable[..., Awaitable[Any]]]):
|
134
175
|
"""
|
135
176
|
Adds an HTTP endpoint to the Flask app for a given use case.
|
136
177
|
|
{bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/bisslog_flask_ws_resolver.py
RENAMED
@@ -5,7 +5,9 @@ This module defines the `BisslogFlaskWebSocketResolver` class, which dynamically
|
|
5
5
|
WebSocket-based use case triggers in a Flask app. The class uses metadata definitions to
|
6
6
|
configure event routes (via `route_key`) and binds them to corresponding use case functions.
|
7
7
|
"""
|
8
|
+
import inspect
|
8
9
|
from typing import Callable
|
10
|
+
|
9
11
|
from flask import Flask, request
|
10
12
|
|
11
13
|
try:
|
@@ -64,13 +66,27 @@ class BisslogFlaskWebSocketResolver(BisslogFlaskResolver):
|
|
64
66
|
base=trigger_info.options.mapper
|
65
67
|
) if trigger_info.options.mapper else None
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
is_async = inspect.iscoroutinefunction(use_case_callable)
|
70
|
+
|
71
|
+
if is_async:
|
72
|
+
@socket_io_obj.on(route_key)
|
73
|
+
async def on_event(data):
|
74
|
+
mapped_data = mapper.map({
|
75
|
+
"route_key": route_key,
|
76
|
+
"connection_id": request.sid,
|
77
|
+
"body": data,
|
78
|
+
"headers": dict(request.headers)
|
79
|
+
}) if mapper else data
|
80
|
+
|
81
|
+
return await use_case_callable(**mapped_data) if mapper else use_case_callable(data)
|
82
|
+
else:
|
83
|
+
@socket_io_obj.on(route_key)
|
84
|
+
def on_event(data):
|
85
|
+
mapped_data = mapper.map({
|
86
|
+
"route_key": route_key,
|
87
|
+
"connection_id": request.sid,
|
88
|
+
"body": data,
|
89
|
+
"headers": dict(request.headers)
|
90
|
+
}) if mapper else data
|
75
91
|
|
76
|
-
|
92
|
+
return use_case_callable(**mapped_data) if mapper else use_case_callable(data)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bisslog_flask
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.3
|
4
4
|
Summary: It is an extension of the bisslog library to support processes with flask
|
5
5
|
Author-email: Darwin Stiven Herrera Cartagena <darwinsherrerac@gmail.com>
|
6
6
|
Project-URL: Homepage, https://github.com/darwinhc/bisslog-flask
|
@@ -10,13 +10,15 @@ Classifier: Operating System :: OS Independent
|
|
10
10
|
Requires-Python: >=3.7
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
-
Requires-Dist: bisslog>=0.0.
|
14
|
-
Requires-Dist: bisslog-schema>=0.0.
|
13
|
+
Requires-Dist: bisslog>=0.0.9
|
14
|
+
Requires-Dist: bisslog-schema>=0.0.10
|
15
15
|
Requires-Dist: flask
|
16
16
|
Provides-Extra: websocket
|
17
17
|
Requires-Dist: flask-socketio; extra == "websocket"
|
18
18
|
Provides-Extra: cors
|
19
19
|
Requires-Dist: flask-cors>=6.0.0; extra == "cors"
|
20
|
+
Provides-Extra: async
|
21
|
+
Requires-Dist: Flask[async]>=2.2; extra == "async"
|
20
22
|
Dynamic: license-file
|
21
23
|
|
22
24
|
# bisslog-flask
|
@@ -28,10 +28,12 @@ bisslog_flask/initializer/bisslog_flask_ws_resolver.py
|
|
28
28
|
bisslog_flask/initializer/init_flask_app_manager.py
|
29
29
|
bisslog_flask/socket_helper/__init__.py
|
30
30
|
bisslog_flask/socket_helper/socket_helper.py
|
31
|
+
requirements/requirements-async.txt
|
31
32
|
requirements/requirements-flask-optional.txt
|
32
33
|
tests/__init__.py
|
33
34
|
tests/unit/__init__.py
|
34
35
|
tests/unit/test_http_resolver.py
|
36
|
+
tests/unit/test_http_resolver_lambda_fn.py
|
35
37
|
tests/unit/test_init_app_manager.py
|
36
38
|
tests/unit/test_ws_resolver.py
|
37
39
|
tests/unit/builder/__init__.py
|
@@ -12,8 +12,8 @@ authors = [
|
|
12
12
|
]
|
13
13
|
requires-python = ">=3.7"
|
14
14
|
dependencies = [
|
15
|
-
"bisslog>=0.0.
|
16
|
-
"bisslog-schema>=0.0.
|
15
|
+
"bisslog>=0.0.9",
|
16
|
+
"bisslog-schema>=0.0.10",
|
17
17
|
"flask"
|
18
18
|
]
|
19
19
|
classifiers = [
|
@@ -28,6 +28,7 @@ Homepage = "https://github.com/darwinhc/bisslog-flask"
|
|
28
28
|
[project.optional-dependencies]
|
29
29
|
websocket = ["flask-socketio"]
|
30
30
|
cors = ["flask-cors>=6.0.0"]
|
31
|
+
async = ["Flask[async]>=2.2"]
|
31
32
|
|
32
33
|
[project.scripts]
|
33
34
|
bisslog_flask = "bisslog_flask.cli:main"
|
@@ -0,0 +1 @@
|
|
1
|
+
Flask[async]>=2.2
|
{bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/tests/unit/builder/test_builder_flask_app_manager.py
RENAMED
@@ -1,15 +1,15 @@
|
|
1
1
|
# test_builder_flask_app_manager.py
|
2
2
|
|
3
|
-
import pytest
|
4
|
-
import json
|
5
3
|
from unittest.mock import patch, MagicMock
|
6
|
-
|
7
|
-
|
4
|
+
|
5
|
+
import pytest
|
8
6
|
from bisslog_schema.schema import TriggerHttp, TriggerWebsocket
|
9
7
|
from bisslog_schema.use_case_code_inspector.use_case_code_metadata import (
|
10
8
|
UseCaseCodeInfoClass, UseCaseCodeInfoObject, UseCaseCodeInfo
|
11
9
|
)
|
12
10
|
|
11
|
+
from bisslog_flask.builder.builder_flask_app_manager import BuilderFlaskAppManager
|
12
|
+
|
13
13
|
|
14
14
|
@pytest.mark.parametrize("n_params,expected", [
|
15
15
|
(0, "setup_func()"),
|
@@ -41,14 +41,16 @@ def test_get_bisslog_setup_none(mock_get_setup):
|
|
41
41
|
|
42
42
|
|
43
43
|
def test_generate_use_case_code_build_class():
|
44
|
-
uc = UseCaseCodeInfoClass(name="my_uc", docs="", module="mymodule", class_name="MyClass"
|
44
|
+
uc = UseCaseCodeInfoClass(name="my_uc", docs="", module="mymodule", class_name="MyClass",
|
45
|
+
is_coroutine=False)
|
45
46
|
name, result = BuilderFlaskAppManager._generate_use_case_code_build(uc)
|
46
47
|
assert name == "my_uc_uc"
|
47
48
|
assert "MyClass()" in result.build
|
48
49
|
|
49
50
|
|
50
51
|
def test_generate_use_case_code_build_object():
|
51
|
-
uc = UseCaseCodeInfoObject(name="my_uc", docs="", module="mymodule", var_name="uc_var"
|
52
|
+
uc = UseCaseCodeInfoObject(name="my_uc", docs="", module="mymodule", var_name="uc_var",
|
53
|
+
is_coroutine=False)
|
52
54
|
name, result = BuilderFlaskAppManager._generate_use_case_code_build(uc)
|
53
55
|
assert name == "uc_var"
|
54
56
|
assert result.build == ""
|
@@ -57,22 +59,24 @@ def test_generate_use_case_code_build_object():
|
|
57
59
|
def test_generate_use_case_code_build_invalid_type():
|
58
60
|
with pytest.raises(ValueError):
|
59
61
|
class Invalid(UseCaseCodeInfo): pass
|
60
|
-
BuilderFlaskAppManager._generate_use_case_code_build(Invalid("x", "", ""))
|
62
|
+
BuilderFlaskAppManager._generate_use_case_code_build(Invalid("x", "", "", False))
|
61
63
|
|
62
64
|
|
63
65
|
def test_generate_use_case_code_http_trigger_without_mapper():
|
64
66
|
trigger = TriggerHttp(path="/hello", method="POST", mapper=None)
|
65
|
-
uc_info = UseCaseCodeInfoClass(name="myuc", docs="", module="mymod", class_name="UCClass"
|
67
|
+
uc_info = UseCaseCodeInfoClass(name="myuc", docs="", module="mymod", class_name="UCClass",
|
68
|
+
is_coroutine=False)
|
66
69
|
result = BuilderFlaskAppManager._generate_use_case_code_http_trigger(
|
67
70
|
"my_uc", "my_uc_uc", uc_info, trigger, 1)
|
68
|
-
assert "def my_uc_handler_1()" in result.body
|
71
|
+
assert "def my_uc_handler_1(**route_vars)" in result.body
|
69
72
|
assert "my_uc_uc(**kwargs)" in result.body
|
70
73
|
assert "request.get_json" in result.body
|
71
74
|
|
72
75
|
|
73
76
|
def test_generate_use_case_code_http_trigger_with_mapper():
|
74
77
|
trigger = TriggerHttp(path="/hi", method="GET", mapper={"body": {"x": "int"}})
|
75
|
-
uc_info = UseCaseCodeInfoClass(name="myuc", docs="", module="mymod", class_name="UCClass"
|
78
|
+
uc_info = UseCaseCodeInfoClass(name="myuc", docs="", module="mymod", class_name="UCClass",
|
79
|
+
is_coroutine=False)
|
76
80
|
result = BuilderFlaskAppManager._generate_use_case_code_http_trigger(
|
77
81
|
"my_uc", "my_uc_uc", uc_info, trigger, 2)
|
78
82
|
assert "res_map" in result.body
|
@@ -81,7 +85,8 @@ def test_generate_use_case_code_http_trigger_with_mapper():
|
|
81
85
|
|
82
86
|
def test_generate_use_case_code_websocket_trigger_with_mapper():
|
83
87
|
trigger = TriggerWebsocket(route_key="room", mapper={"body": {"msg": "str"}})
|
84
|
-
uc_info = UseCaseCodeInfoObject(name="ws_uc", docs="", module="wsmod", var_name="ws_callable"
|
88
|
+
uc_info = UseCaseCodeInfoObject(name="ws_uc", docs="", module="wsmod", var_name="ws_callable",
|
89
|
+
is_coroutine=False)
|
85
90
|
result = BuilderFlaskAppManager._generate_use_case_code_websocket_trigger(
|
86
91
|
"chat", "ws_callable", uc_info, trigger, 0)
|
87
92
|
assert "res_map = chat_ws_mapper_0.map" in result.build
|
@@ -91,7 +96,8 @@ def test_generate_use_case_code_websocket_trigger_with_mapper():
|
|
91
96
|
|
92
97
|
def test_generate_use_case_code_websocket_trigger_without_mapper():
|
93
98
|
trigger = TriggerWebsocket(route_key=None, mapper=None)
|
94
|
-
uc_info = UseCaseCodeInfoObject(name="ws_uc", docs="", module="wsmod", var_name="ws_callable"
|
99
|
+
uc_info = UseCaseCodeInfoObject(name="ws_uc", docs="", module="wsmod", var_name="ws_callable",
|
100
|
+
is_coroutine=False)
|
95
101
|
result = BuilderFlaskAppManager._generate_use_case_code_websocket_trigger(
|
96
102
|
"chat", "ws_callable", uc_info, trigger, 1)
|
97
103
|
assert "payload = json.loads(data)" in result.build
|
@@ -1,14 +1,15 @@
|
|
1
1
|
# test_builder_flask_app_manager_call.py
|
2
2
|
|
3
|
-
import pytest
|
4
3
|
from unittest.mock import patch, MagicMock
|
5
|
-
|
6
|
-
from bisslog_flask.builder.static_python_construct_data import StaticPythonConstructData
|
4
|
+
|
7
5
|
from bisslog_schema.schema import TriggerHttp, TriggerWebsocket, UseCaseInfo
|
8
6
|
from bisslog_schema.use_case_code_inspector.use_case_code_metadata import (
|
9
|
-
UseCaseCodeInfoClass
|
7
|
+
UseCaseCodeInfoClass
|
10
8
|
)
|
11
9
|
|
10
|
+
from bisslog_flask.builder.builder_flask_app_manager import BuilderFlaskAppManager
|
11
|
+
from bisslog_flask.builder.static_python_construct_data import StaticPythonConstructData
|
12
|
+
|
12
13
|
|
13
14
|
@patch("bisslog_flask.builder.builder_flask_app_manager.read_full_service_metadata")
|
14
15
|
@patch.object(BuilderFlaskAppManager, "_get_bisslog_setup")
|
@@ -41,7 +42,8 @@ def test_call_generates_complete_flask_code(
|
|
41
42
|
|
42
43
|
discovered_use_cases = {
|
43
44
|
"my_use_case": UseCaseCodeInfoClass(
|
44
|
-
name="my_use_case", docs=None, module="uc_module", class_name="UseCase"
|
45
|
+
name="my_use_case", docs=None, module="uc_module", class_name="UseCase",
|
46
|
+
is_coroutine=False
|
45
47
|
)
|
46
48
|
}
|
47
49
|
|
@@ -0,0 +1,268 @@
|
|
1
|
+
"""Tests for bisslog_flask.initializer.bisslog_flask_http_resolver."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
from unittest.mock import Mock
|
7
|
+
|
8
|
+
import pytest
|
9
|
+
import flask
|
10
|
+
from flask import Flask
|
11
|
+
|
12
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerInfo
|
13
|
+
from bisslog_schema.schema.triggers.trigger_mappable import TriggerMappable
|
14
|
+
|
15
|
+
from bisslog_flask.initializer.bisslog_flask_http_resolver import BisslogFlaskHttpResolver
|
16
|
+
|
17
|
+
|
18
|
+
pytestmark = pytest.mark.skipif(
|
19
|
+
tuple(map(int, flask.__version__.split(".")[:2])) < (2, 2),
|
20
|
+
reason="Async views need Flask >= 2.2",
|
21
|
+
)
|
22
|
+
|
23
|
+
@pytest.fixture
|
24
|
+
def flask_app():
|
25
|
+
app = Flask(__name__)
|
26
|
+
return app
|
27
|
+
|
28
|
+
|
29
|
+
@pytest.fixture
|
30
|
+
def resolver():
|
31
|
+
return BisslogFlaskHttpResolver()
|
32
|
+
|
33
|
+
|
34
|
+
def make_uc_info(key="uc"):
|
35
|
+
return UseCaseInfo(
|
36
|
+
keyname=key, name=key, description="", type="sync", triggers=[]
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
def test_register_get_route_without_mapper(flask_app, resolver):
|
42
|
+
def mock_uc():
|
43
|
+
return {"status": "ok"}
|
44
|
+
|
45
|
+
info = make_uc_info("test_uc")
|
46
|
+
trig_http = TriggerHttp(method="GET", path="/test", allow_cors=False)
|
47
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
48
|
+
|
49
|
+
resolver(flask_app, info, trig, mock_uc)
|
50
|
+
|
51
|
+
client = flask_app.test_client()
|
52
|
+
r = client.get("/test")
|
53
|
+
|
54
|
+
assert r.status_code == 200
|
55
|
+
assert r.json == {"status": "ok"}
|
56
|
+
|
57
|
+
|
58
|
+
def test_register_post_route_with_json(flask_app, resolver):
|
59
|
+
def mock_uc(**data):
|
60
|
+
return {"echo": data.get("name")}
|
61
|
+
|
62
|
+
info = make_uc_info("echo_uc")
|
63
|
+
trig_http = TriggerHttp(method="POST", path="/echo", allow_cors=False)
|
64
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
65
|
+
|
66
|
+
resolver(flask_app, info, trig, mock_uc)
|
67
|
+
|
68
|
+
client = flask_app.test_client()
|
69
|
+
r = client.post("/echo", json={"name": "ChatGPT"})
|
70
|
+
|
71
|
+
assert r.status_code == 200
|
72
|
+
assert r.json == {"echo": "ChatGPT"}
|
73
|
+
|
74
|
+
|
75
|
+
def test_register_post_route_without_body_ok(flask_app, resolver):
|
76
|
+
|
77
|
+
def mock_uc(**kwargs):
|
78
|
+
return {"kwargs": kwargs}
|
79
|
+
|
80
|
+
info = make_uc_info("no_body")
|
81
|
+
trig_http = TriggerHttp(method="POST", path="/no-body", allow_cors=False)
|
82
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
83
|
+
|
84
|
+
resolver(flask_app, info, trig, mock_uc)
|
85
|
+
|
86
|
+
client = flask_app.test_client()
|
87
|
+
r = client.post("/no-body")
|
88
|
+
|
89
|
+
assert r.status_code == 200
|
90
|
+
assert r.json == {"kwargs": {}}
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
def test_register_post_route_with_mapper(flask_app, resolver):
|
95
|
+
def mock_uc(*, a=None, b=None, c=None, d=None, e=None, f=None):
|
96
|
+
return {"a": a, "b": b, "c": c, "d": d, "e": e, "f": f}
|
97
|
+
|
98
|
+
info = make_uc_info("echo_uc")
|
99
|
+
mapper = {
|
100
|
+
"body.algo1": "a",
|
101
|
+
"body.algo2": "b",
|
102
|
+
"path_query.algo3": "c",
|
103
|
+
"headers.algo4": "d",
|
104
|
+
"params.algo5": "e",
|
105
|
+
"params.algo6": "f",
|
106
|
+
}
|
107
|
+
trig_http = TriggerHttp(
|
108
|
+
method="POST",
|
109
|
+
path="/echo/{algo3}",
|
110
|
+
allow_cors=False,
|
111
|
+
mapper=mapper,
|
112
|
+
)
|
113
|
+
assert isinstance(trig_http, TriggerMappable)
|
114
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
115
|
+
|
116
|
+
resolver(flask_app, info, trig, mock_uc)
|
117
|
+
|
118
|
+
client = flask_app.test_client()
|
119
|
+
r = client.post(
|
120
|
+
"/echo/something",
|
121
|
+
json={"algo1": 2356, "algo2": "casa"},
|
122
|
+
headers=[("algo4", "prueba4")],
|
123
|
+
query_string={"algo5": 7554, "algo6": "prueba6"},
|
124
|
+
)
|
125
|
+
|
126
|
+
assert r.status_code == 200
|
127
|
+
payload = r.get_json()
|
128
|
+
assert payload["a"] == 2356
|
129
|
+
assert payload["b"] == "casa"
|
130
|
+
assert payload["c"] == "something"
|
131
|
+
assert payload["d"] == "prueba4"
|
132
|
+
assert payload["e"] == "7554"
|
133
|
+
assert payload["f"] == "prueba6"
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
def test_register_async_function(flask_app, resolver):
|
138
|
+
async def mock_uc(**data):
|
139
|
+
await asyncio.sleep(0)
|
140
|
+
return {"ok": True, "data": data}
|
141
|
+
|
142
|
+
info = make_uc_info("async_fn")
|
143
|
+
trig_http = TriggerHttp(method="POST", path="/async-fn", allow_cors=False)
|
144
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
145
|
+
|
146
|
+
resolver(flask_app, info, trig, mock_uc)
|
147
|
+
|
148
|
+
client = flask_app.test_client()
|
149
|
+
r = client.post("/async-fn", json={"a": 1})
|
150
|
+
assert r.status_code == 200
|
151
|
+
assert r.json == {"ok": True, "data": {"a": 1}}
|
152
|
+
|
153
|
+
|
154
|
+
def test_register_async_callable_object(flask_app, resolver):
|
155
|
+
class UC:
|
156
|
+
async def __call__(self, *, a=None):
|
157
|
+
await asyncio.sleep(0)
|
158
|
+
return {"a": a}
|
159
|
+
|
160
|
+
info = make_uc_info("async_obj")
|
161
|
+
trig_http = TriggerHttp(
|
162
|
+
method="POST",
|
163
|
+
path="/async-obj",
|
164
|
+
allow_cors=False,
|
165
|
+
mapper={"body.a": "a"},
|
166
|
+
)
|
167
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
168
|
+
|
169
|
+
resolver(flask_app, info, trig, UC())
|
170
|
+
|
171
|
+
client = flask_app.test_client()
|
172
|
+
r = client.post("/async-obj", json={"a": 7})
|
173
|
+
assert r.status_code == 200
|
174
|
+
assert r.json == {"a": 7}
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
def test_invalid_trigger_type_is_ignored(flask_app, resolver):
|
179
|
+
trig = TriggerInfo(keyname="invalid", type="websocket", options=Mock())
|
180
|
+
mock_uc = Mock()
|
181
|
+
info = make_uc_info("invalid_uc")
|
182
|
+
|
183
|
+
resolver(flask_app, info, trig, mock_uc)
|
184
|
+
|
185
|
+
client = flask_app.test_client()
|
186
|
+
r = client.get("/invalid")
|
187
|
+
assert r.status_code == 404
|
188
|
+
|
189
|
+
|
190
|
+
def test_endpoint_name_contains_key_and_path(flask_app, resolver):
|
191
|
+
def mock_uc(id):
|
192
|
+
return {"ok": 1}
|
193
|
+
|
194
|
+
info = make_uc_info("keyX")
|
195
|
+
trig_http = TriggerHttp(method="GET", path="/items/{id}", allow_cors=False)
|
196
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
197
|
+
|
198
|
+
resolver(flask_app, info, trig, mock_uc)
|
199
|
+
|
200
|
+
|
201
|
+
expected_rule = "keyX " + "/items/<id>"
|
202
|
+
found = any(r.rule == "/items/<id>" and r.endpoint == expected_rule for r in flask_app.url_map.iter_rules())
|
203
|
+
assert found
|
204
|
+
|
205
|
+
client = flask_app.test_client()
|
206
|
+
r = client.get("/items/42")
|
207
|
+
assert r.status_code == 200
|
208
|
+
assert r.json == {"ok": 1}
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
def test_cors_applied_when_allowed(monkeypatch, flask_app, resolver):
|
213
|
+
|
214
|
+
captured = {}
|
215
|
+
|
216
|
+
def fake_cross_origin(**kwargs):
|
217
|
+
captured.update(kwargs)
|
218
|
+
def decorator(fn):
|
219
|
+
return fn
|
220
|
+
return decorator
|
221
|
+
|
222
|
+
|
223
|
+
import bisslog_flask.initializer.bisslog_flask_http_resolver as mod
|
224
|
+
monkeypatch.setattr(mod, "cross_origin", fake_cross_origin, raising=True)
|
225
|
+
|
226
|
+
def mock_uc():
|
227
|
+
return {"ok": True}
|
228
|
+
|
229
|
+
info = make_uc_info("cors_uc")
|
230
|
+
trig_http = TriggerHttp(
|
231
|
+
method="GET",
|
232
|
+
path="/cors",
|
233
|
+
allow_cors=True,
|
234
|
+
allowed_origins=["https://a.com", "https://b.com"],
|
235
|
+
)
|
236
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
237
|
+
|
238
|
+
resolver(flask_app, info, trig, mock_uc)
|
239
|
+
|
240
|
+
|
241
|
+
assert captured["origins"] == ["https://a.com", "https://b.com"]
|
242
|
+
assert captured["methods"] == ["GET"]
|
243
|
+
assert captured["supports_credentials"] is True
|
244
|
+
assert "allow_headers" in captured
|
245
|
+
|
246
|
+
client = flask_app.test_client()
|
247
|
+
r = client.get("/cors")
|
248
|
+
assert r.status_code == 200
|
249
|
+
assert r.json == {"ok": True}
|
250
|
+
|
251
|
+
|
252
|
+
def test_cors_raises_when_flask_cors_missing(monkeypatch, flask_app):
|
253
|
+
|
254
|
+
import bisslog_flask.initializer.bisslog_flask_http_resolver as mod
|
255
|
+
monkeypatch.setattr(mod, "cross_origin", None, raising=True)
|
256
|
+
|
257
|
+
resolver = BisslogFlaskHttpResolver()
|
258
|
+
|
259
|
+
def mock_uc():
|
260
|
+
return {"ok": True}
|
261
|
+
|
262
|
+
info = make_uc_info("cors_missing")
|
263
|
+
trig_http = TriggerHttp(method="GET", path="/cors-missing", allow_cors=True)
|
264
|
+
trig = TriggerInfo(keyname="t", type="http", options=trig_http)
|
265
|
+
|
266
|
+
with pytest.raises(ImportError):
|
267
|
+
|
268
|
+
resolver(flask_app, info, trig, mock_uc)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
from flask import Flask
|
5
|
+
from unittest.mock import Mock
|
6
|
+
import flask
|
7
|
+
import pytest
|
8
|
+
|
9
|
+
from bisslog_flask.initializer.bisslog_flask_http_resolver import BisslogFlaskHttpResolver
|
10
|
+
|
11
|
+
pytestmark = pytest.mark.skipif(
|
12
|
+
tuple(map(int, flask.__version__.split(".")[:2])) < (2, 2),
|
13
|
+
reason="Async views need Flask >= 2.2",
|
14
|
+
)
|
15
|
+
|
16
|
+
@pytest.fixture
|
17
|
+
def app():
|
18
|
+
return Flask(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
def test_lambda_fn_without_mapper_get_does_not_include_body(app):
|
22
|
+
called = {}
|
23
|
+
|
24
|
+
def uc(**kwargs):
|
25
|
+
called.update(kwargs)
|
26
|
+
return {"ok": True}
|
27
|
+
|
28
|
+
with app.test_request_context("/items/42?q=abc", method="GET"):
|
29
|
+
resp = BisslogFlaskHttpResolver._lambda_fn(fn=uc, __mapper__=None, id="42")
|
30
|
+
assert resp.mimetype == "application/json"
|
31
|
+
assert resp.get_json() == {"ok": True}
|
32
|
+
|
33
|
+
assert called == {"id": "42"}
|
34
|
+
|
35
|
+
|
36
|
+
def test_lambda_fn_without_mapper_post_includes_body(app):
|
37
|
+
captured = {}
|
38
|
+
|
39
|
+
def uc(**kwargs):
|
40
|
+
captured.update(kwargs)
|
41
|
+
return {"received": kwargs}
|
42
|
+
|
43
|
+
with app.test_request_context("/echo", method="POST", json={"a": 1, "b": "x"}):
|
44
|
+
resp = BisslogFlaskHttpResolver._lambda_fn(fn=uc, __mapper__=None)
|
45
|
+
assert resp.status_code == 200
|
46
|
+
assert resp.get_json() == {"received": {"a": 1, "b": "x"}}
|
47
|
+
|
48
|
+
assert captured == {"a": 1, "b": "x"}
|
49
|
+
|
50
|
+
|
51
|
+
def test_lambda_fn_with_mapper_maps_all_parts_and_calls_uc(app):
|
52
|
+
class FakeMapper:
|
53
|
+
def __init__(self):
|
54
|
+
self.last_input = None
|
55
|
+
def map(self, payload):
|
56
|
+
self.last_input = payload
|
57
|
+
return {"x": 99}
|
58
|
+
|
59
|
+
mapper = FakeMapper()
|
60
|
+
uc = Mock(return_value={"ok": True})
|
61
|
+
|
62
|
+
with app.test_request_context(
|
63
|
+
"/do/7?key=vv",
|
64
|
+
method="POST",
|
65
|
+
json={"foo": 1},
|
66
|
+
headers={"X-Test": "hdr"},
|
67
|
+
):
|
68
|
+
from flask import request
|
69
|
+
request.view_args = {"id": "7"}
|
70
|
+
|
71
|
+
resp = BisslogFlaskHttpResolver._lambda_fn(fn=uc, __mapper__=mapper)
|
72
|
+
|
73
|
+
assert resp.status_code == 200
|
74
|
+
assert resp.get_json() == {"ok": True}
|
75
|
+
|
76
|
+
inp = mapper.last_input
|
77
|
+
assert set(inp.keys()) == {"path_query", "body", "params", "headers"}
|
78
|
+
assert inp["path_query"] == {"id": "7"}
|
79
|
+
assert inp["body"] == {"foo": 1}
|
80
|
+
assert inp["params"] == {"key": "vv"}
|
81
|
+
assert inp["headers"].get("X-Test") == "hdr"
|
82
|
+
|
83
|
+
uc.assert_called_once_with(x=99)
|
84
|
+
|
85
|
+
|
86
|
+
def test_lambda_fn_returns_json_response_type(app):
|
87
|
+
def uc():
|
88
|
+
return {"hello": "world"}
|
89
|
+
|
90
|
+
with app.test_request_context("/hi", method="GET"):
|
91
|
+
resp = BisslogFlaskHttpResolver._lambda_fn(fn=uc, __mapper__=None)
|
92
|
+
assert resp.mimetype == "application/json"
|
93
|
+
assert resp.get_json() == {"hello": "world"}
|
@@ -1,13 +1,14 @@
|
|
1
|
-
import
|
2
|
-
from flask import Flask
|
1
|
+
from types import SimpleNamespace
|
3
2
|
from unittest.mock import MagicMock
|
4
3
|
|
5
|
-
|
4
|
+
import pytest
|
6
5
|
from bisslog_schema.schema import TriggerInfo
|
7
6
|
from bisslog_schema.schema.triggers.trigger_http import TriggerHttp
|
8
7
|
from bisslog_schema.schema.triggers.trigger_websocket import TriggerWebsocket
|
9
8
|
from bisslog_schema.schema.use_case_info import UseCaseInfo
|
10
|
-
from
|
9
|
+
from flask import Flask
|
10
|
+
|
11
|
+
from bisslog_flask.initializer.init_flask_app_manager import InitFlaskAppManager
|
11
12
|
|
12
13
|
|
13
14
|
@pytest.fixture
|
@@ -23,6 +24,18 @@ def fake_use_case_info():
|
|
23
24
|
triggers=[http_trigger, ws_trigger]
|
24
25
|
)
|
25
26
|
|
27
|
+
@pytest.fixture
|
28
|
+
def fake_use_case_info_with_async():
|
29
|
+
http_trigger = TriggerInfo(keyname="http_trigger", type="http", options=TriggerHttp(method="GET", path="/test2"))
|
30
|
+
|
31
|
+
return UseCaseInfo(
|
32
|
+
keyname="sample_coroutine_uc",
|
33
|
+
name="sample_coroutine_uc",
|
34
|
+
description="desc",
|
35
|
+
type="async",
|
36
|
+
triggers=[http_trigger]
|
37
|
+
)
|
38
|
+
|
26
39
|
@pytest.fixture
|
27
40
|
def fake_force_import():
|
28
41
|
force_import = MagicMock()
|
@@ -30,13 +43,17 @@ def fake_force_import():
|
|
30
43
|
|
31
44
|
|
32
45
|
@pytest.fixture
|
33
|
-
def mock_read_service_info_with_code(fake_use_case_info):
|
46
|
+
def mock_read_service_info_with_code(fake_use_case_info, fake_use_case_info_with_async):
|
47
|
+
|
48
|
+
async def mock_coroutine(*args, **kwargs):
|
49
|
+
return {"sample_coroutine_res": 23}
|
50
|
+
|
34
51
|
return SimpleNamespace(
|
35
52
|
declared_metadata=SimpleNamespace(
|
36
53
|
name="TestService",
|
37
|
-
use_cases={"sample_uc": fake_use_case_info}
|
54
|
+
use_cases={"sample_uc": fake_use_case_info, "sample_coroutine_uc": fake_use_case_info_with_async}
|
38
55
|
),
|
39
|
-
discovered_use_cases={"sample_uc": lambda: {"ok": True}}
|
56
|
+
discovered_use_cases={"sample_uc": lambda: {"ok": True}, "sample_coroutine_uc": mock_coroutine},
|
40
57
|
)
|
41
58
|
|
42
59
|
|
@@ -57,7 +74,7 @@ def test_init_flask_app_registers_routes(monkeypatch, fake_use_case_info,
|
|
57
74
|
|
58
75
|
# Assert
|
59
76
|
assert isinstance(app, Flask)
|
60
|
-
mock_http.
|
77
|
+
assert mock_http.call_count == 2
|
61
78
|
mock_ws.assert_called_once()
|
62
79
|
|
63
80
|
|
@@ -1,11 +1,12 @@
|
|
1
|
+
from unittest.mock import Mock, MagicMock
|
2
|
+
|
1
3
|
import pytest
|
4
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerInfo, TriggerConsumer
|
5
|
+
from bisslog_schema.schema.triggers.trigger_websocket import TriggerWebsocket
|
2
6
|
from flask import Flask
|
3
7
|
from flask_socketio import SocketIO
|
4
|
-
from unittest.mock import Mock, MagicMock
|
5
8
|
|
6
9
|
from bisslog_flask.initializer.bisslog_flask_ws_resolver import BisslogFlaskWebSocketResolver
|
7
|
-
from bisslog_schema.schema import UseCaseInfo, TriggerInfo, TriggerConsumer
|
8
|
-
from bisslog_schema.schema.triggers.trigger_websocket import TriggerWebsocket
|
9
10
|
|
10
11
|
|
11
12
|
@pytest.fixture
|
@@ -1,97 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from flask import Flask
|
3
|
-
from unittest.mock import Mock
|
4
|
-
|
5
|
-
from bisslog_flask.initializer.bisslog_flask_http_resolver import BisslogFlaskHttpResolver
|
6
|
-
from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerInfo
|
7
|
-
|
8
|
-
|
9
|
-
@pytest.fixture
|
10
|
-
def flask_app():
|
11
|
-
app = Flask(__name__)
|
12
|
-
return app
|
13
|
-
|
14
|
-
|
15
|
-
@pytest.fixture
|
16
|
-
def resolver():
|
17
|
-
return BisslogFlaskHttpResolver()
|
18
|
-
|
19
|
-
|
20
|
-
def test_register_get_route_without_mapper(flask_app, resolver):
|
21
|
-
def mock_uc():
|
22
|
-
return {"status": "ok"}
|
23
|
-
|
24
|
-
use_case_info = UseCaseInfo(
|
25
|
-
keyname="test_uc", name="Test UC", description="", type="sync", triggers=[]
|
26
|
-
)
|
27
|
-
trigger_http = TriggerHttp(method="GET", path="/test", allow_cors=False)
|
28
|
-
trigger = TriggerInfo(keyname="test_trigger", type="http", options=trigger_http)
|
29
|
-
|
30
|
-
resolver(flask_app, use_case_info, trigger, mock_uc)
|
31
|
-
|
32
|
-
client = flask_app.test_client()
|
33
|
-
response = client.get("/test")
|
34
|
-
|
35
|
-
assert response.status_code == 200
|
36
|
-
assert response.json == {"status": "ok"}
|
37
|
-
|
38
|
-
|
39
|
-
def test_register_post_route_with_json(flask_app, resolver):
|
40
|
-
def mock_uc(**data):
|
41
|
-
return {"echo": data.get("name")}
|
42
|
-
|
43
|
-
use_case_info = UseCaseInfo(
|
44
|
-
keyname="echo_uc", name="Echo UC", description="", type="sync", triggers=[]
|
45
|
-
)
|
46
|
-
trigger_http = TriggerHttp(method="POST", path="/echo", allow_cors=False)
|
47
|
-
trigger = TriggerInfo(keyname="echo_trigger", type="http", options=trigger_http)
|
48
|
-
|
49
|
-
resolver(flask_app, use_case_info, trigger, mock_uc)
|
50
|
-
|
51
|
-
client = flask_app.test_client()
|
52
|
-
response = client.post("/echo", json={"name": "ChatGPT"})
|
53
|
-
|
54
|
-
assert response.status_code == 200
|
55
|
-
assert response.json == {"echo": "ChatGPT"}
|
56
|
-
|
57
|
-
|
58
|
-
def test_register_post_route_with_mapper(flask_app, resolver):
|
59
|
-
def mock_uc(*, a = None, b = None, c = None, d = None, e = None, f = None):
|
60
|
-
return {"a": a, "b": b, "c": c, "d": d, "e": e, "f": f}
|
61
|
-
|
62
|
-
use_case_info = UseCaseInfo(
|
63
|
-
keyname="echo_uc", name="Echo UC", description="", type="sync", triggers=[]
|
64
|
-
)
|
65
|
-
trigger_http = TriggerHttp(method="POST", path="/echo/{algo3}", allow_cors=False,
|
66
|
-
mapper={"body.algo1": "a", "body.algo2": "b",
|
67
|
-
"path_query.algo3": "c", "headers.algo4": "d",
|
68
|
-
"params.algo5": "e", "params.algo6": "f"})
|
69
|
-
trigger = TriggerInfo(keyname="echo_trigger", type="http", options=trigger_http)
|
70
|
-
|
71
|
-
resolver(flask_app, use_case_info, trigger, mock_uc)
|
72
|
-
|
73
|
-
client = flask_app.test_client()
|
74
|
-
response = client.post("/echo/something", json={"algo1": 2356, "algo2": "casa"},
|
75
|
-
headers=[("algo4", "prueba4")], query_string={"algo5": 7554, "algo6": "prueba6"})
|
76
|
-
|
77
|
-
assert response.status_code == 200
|
78
|
-
assert response.json["a"] == 2356
|
79
|
-
assert response.json["b"] == "casa"
|
80
|
-
assert response.json["c"] == "something"
|
81
|
-
assert response.json["d"] == "prueba4"
|
82
|
-
assert response.json["e"] == "7554"
|
83
|
-
assert response.json["f"] == "prueba6"
|
84
|
-
|
85
|
-
def test_invalid_trigger_type_is_ignored(flask_app, resolver):
|
86
|
-
trigger = TriggerInfo(keyname="invalid", type="websocket", options=Mock())
|
87
|
-
mock_uc = Mock()
|
88
|
-
use_case_info = UseCaseInfo(
|
89
|
-
keyname="invalid_uc", name="", description="", type="sync", triggers=[]
|
90
|
-
)
|
91
|
-
|
92
|
-
resolver(flask_app, use_case_info, trigger, mock_uc)
|
93
|
-
|
94
|
-
client = flask_app.test_client()
|
95
|
-
response = client.get("/invalid")
|
96
|
-
|
97
|
-
assert response.status_code == 404
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/builder/static_python_construct_data.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{bisslog_flask-0.0.2 → bisslog_flask-0.0.3}/bisslog_flask/initializer/init_flask_app_manager.py
RENAMED
@@ -19,8 +19,8 @@ from typing import Optional, Callable
|
|
19
19
|
|
20
20
|
from bisslog_schema import read_service_info_with_code
|
21
21
|
from bisslog_schema.eager_import_module_or_package import EagerImportModulePackage
|
22
|
-
from bisslog_schema.setup import run_setup
|
23
22
|
from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerWebsocket
|
23
|
+
from bisslog_schema.setup import run_setup
|
24
24
|
from flask import Flask
|
25
25
|
|
26
26
|
from .bisslog_flask_http_resolver import BisslogFlaskHttpResolver
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|