bisslog-flask 0.0.1__tar.gz → 0.0.2__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.1 → bisslog_flask-0.0.2}/PKG-INFO +56 -43
- bisslog_flask-0.0.2/README.md +133 -0
- bisslog_flask-0.0.2/bisslog_flask/builder/builder_flask_app_manager.py +353 -0
- bisslog_flask-0.0.2/bisslog_flask/builder/static_python_construct_data.py +172 -0
- bisslog_flask-0.0.2/bisslog_flask/cli/__init__.py +88 -0
- bisslog_flask-0.0.2/bisslog_flask/cli/commands/build.py +55 -0
- bisslog_flask-0.0.2/bisslog_flask/cli/commands/run.py +52 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/init_flask_app_manager.py +16 -2
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/socket_helper/socket_helper.py +0 -3
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/PKG-INFO +56 -43
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/SOURCES.txt +17 -1
- bisslog_flask-0.0.2/bisslog_flask.egg-info/entry_points.txt +2 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/requires.txt +1 -1
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/pylintrc +4 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/pyproject.toml +4 -1
- bisslog_flask-0.0.2/requirements.txt +3 -0
- bisslog_flask-0.0.2/tests/__init__.py +0 -0
- bisslog_flask-0.0.2/tests/unit/__init__.py +0 -0
- bisslog_flask-0.0.2/tests/unit/builder/__init__.py +0 -0
- bisslog_flask-0.0.2/tests/unit/builder/test_builder_flask_app_manager.py +99 -0
- bisslog_flask-0.0.2/tests/unit/builder/test_builder_full.py +62 -0
- bisslog_flask-0.0.2/tests/unit/builder/test_static_python_res.py +72 -0
- bisslog_flask-0.0.2/tests/unit/cli/__init__.py +0 -0
- bisslog_flask-0.0.2/tests/unit/cli/test_build.py +31 -0
- bisslog_flask-0.0.2/tests/unit/cli/test_cli.py +70 -0
- bisslog_flask-0.0.2/tests/unit/cli/test_run.py +31 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/tests/unit/test_init_app_manager.py +11 -4
- bisslog_flask-0.0.1/README.md +0 -120
- bisslog_flask-0.0.1/requirements.txt +0 -3
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.github/workflows/publish-pypi.yml +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.github/workflows/publish-test-pypi.yml +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.github/workflows/receive-changes.yml +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.gitignore +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/LICENSE +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/__init__.py +0 -0
- {bisslog_flask-0.0.1/bisslog_flask/initializer → bisslog_flask-0.0.2/bisslog_flask/builder}/__init__.py +0 -0
- {bisslog_flask-0.0.1/bisslog_flask/socket_helper → bisslog_flask-0.0.2/bisslog_flask/cli/commands}/__init__.py +0 -0
- {bisslog_flask-0.0.1/tests → bisslog_flask-0.0.2/bisslog_flask/initializer}/__init__.py +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/bisslog_flask_http_resolver.py +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/bisslog_flask_resolver.py +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/bisslog_flask_ws_resolver.py +0 -0
- {bisslog_flask-0.0.1/tests/unit → bisslog_flask-0.0.2/bisslog_flask/socket_helper}/__init__.py +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/dependency_links.txt +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/top_level.txt +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/requirements/requirements-flask-optional.txt +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/setup.cfg +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/tests/unit/test_http_resolver.py +0 -0
- {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/tests/unit/test_ws_resolver.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.2
|
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
|
@@ -11,7 +11,7 @@ Requires-Python: >=3.7
|
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
13
|
Requires-Dist: bisslog>=0.0.7
|
14
|
-
Requires-Dist: bisslog-schema>=0.0.
|
14
|
+
Requires-Dist: bisslog-schema>=0.0.6
|
15
15
|
Requires-Dist: flask
|
16
16
|
Provides-Extra: websocket
|
17
17
|
Requires-Dist: flask-socketio; extra == "websocket"
|
@@ -19,45 +19,43 @@ Provides-Extra: cors
|
|
19
19
|
Requires-Dist: flask-cors>=6.0.0; extra == "cors"
|
20
20
|
Dynamic: license-file
|
21
21
|
|
22
|
-
|
23
22
|
# bisslog-flask
|
24
23
|
|
25
24
|
[](https://pypi.org/project/bisslog-flask/)
|
26
25
|
[](LICENSE)
|
27
26
|
|
28
|
-
**bisslog-flask** is an extension of the
|
27
|
+
**bisslog-flask** is an extension of the Bisslog library to support processes with Flask.
|
28
|
+
It enables dynamic HTTP and WebSocket route registration from use case metadata, allowing developers to build clean, modular, and metadata-driven APIs with minimal boilerplate.
|
29
29
|
|
30
|
-
Part of the
|
30
|
+
Part of the Bisslog ecosystem, it is designed to work seamlessly with domain-centric architectures like Hexagonal or Clean Architecture.
|
31
31
|
|
32
|
-
|
32
|
+
---
|
33
33
|
|
34
|
-
|
34
|
+
## ✨ Features
|
35
35
|
|
36
|
+
- 🔁 Dynamic route registration for HTTP and WebSocket triggers
|
36
37
|
- 🧠 Metadata-driven setup – use YAML or JSON to declare your use cases
|
37
|
-
|
38
|
-
- 🔒 Automatic CORS per endpoint using flask-cors
|
39
|
-
|
38
|
+
- 🔒 Automatic CORS per endpoint using `flask-cors`
|
40
39
|
- 🔌 Extensible resolver pattern – plug in your own processor
|
41
|
-
|
42
40
|
- ⚙️ Mapper integration – maps HTTP request parts to domain function arguments
|
43
41
|
|
44
|
-
|
42
|
+
---
|
45
43
|
|
46
44
|
## 📦 Installation
|
47
45
|
|
48
|
-
|
46
|
+
```bash
|
49
47
|
pip install bisslog-flask
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
```
|
53
49
|
|
50
|
+
---
|
54
51
|
|
55
52
|
## 🚀 Quickstart
|
56
53
|
|
57
54
|
### Programmatically
|
58
55
|
|
59
|
-
if you want to configure the app before
|
60
|
-
|
56
|
+
Use this approach if you want to configure the app before Bisslog touches it:
|
57
|
+
|
58
|
+
```python
|
61
59
|
from flask import Flask
|
62
60
|
from bisslog_flask import BisslogFlask
|
63
61
|
|
@@ -70,11 +68,11 @@ BisslogFlask(
|
|
70
68
|
|
71
69
|
if __name__ == "__main__":
|
72
70
|
app.run(debug=True)
|
73
|
-
|
71
|
+
```
|
74
72
|
|
75
|
-
|
73
|
+
Or use the factory version:
|
76
74
|
|
77
|
-
|
75
|
+
```python
|
78
76
|
from bisslog_flask import BisslogFlask
|
79
77
|
|
80
78
|
app = BisslogFlask(
|
@@ -84,58 +82,73 @@ app = BisslogFlask(
|
|
84
82
|
|
85
83
|
if __name__ == "__main__":
|
86
84
|
app.run(debug=True)
|
87
|
-
|
88
|
-
|
85
|
+
```
|
89
86
|
|
87
|
+
---
|
90
88
|
|
91
|
-
##
|
89
|
+
## 🖥️ CLI Usage
|
92
90
|
|
93
|
-
|
91
|
+
You can also use the `bisslog_flask` CLI to run or generate a Flask app.
|
94
92
|
|
93
|
+
```bash
|
94
|
+
bisslog_flask run [--metadata-file FILE] [--use-cases-folder-path DIR]
|
95
|
+
[--infra-folder-path DIR] [--encoding ENC]
|
96
|
+
[--secret-key KEY] [--jwt-secret-key KEY]
|
95
97
|
|
96
|
-
|
98
|
+
bisslog_flask build [--metadata-file FILE] [--use-cases-folder-path DIR]
|
99
|
+
[--infra-folder-path DIR] [--encoding ENC]
|
100
|
+
[--target-filename FILE]
|
101
|
+
```
|
97
102
|
|
98
|
-
|
103
|
+
- `run`: Launches the Flask application from metadata.
|
104
|
+
- `build`: Generates a boilerplate Flask file (`flask_app.py` by default).
|
99
105
|
|
100
|
-
|
106
|
+
All options are optional. You can override defaults via CLI flags.
|
101
107
|
|
102
|
-
|
108
|
+
---
|
103
109
|
|
110
|
+
## 🔐 CORS Handling
|
104
111
|
|
105
|
-
|
112
|
+
CORS is applied only when `allow_cors: true` is specified in the trigger.
|
106
113
|
|
107
|
-
|
114
|
+
Fully dynamic: works even with Flask dynamic routes like `/users/<id>`.
|
108
115
|
|
109
|
-
|
116
|
+
Powered by `@cross_origin` from `flask-cors`.
|
110
117
|
|
111
|
-
|
118
|
+
---
|
112
119
|
|
113
|
-
|
120
|
+
## ✅ Requirements
|
114
121
|
|
115
|
-
|
122
|
+
- Python ≥ 3.7
|
123
|
+
- Flask ≥ 2.0
|
124
|
+
- bisslog-schema ≥ 0.0.3
|
125
|
+
- flask-cors
|
126
|
+
- (Optional) flask-sock if using WebSocket triggers
|
116
127
|
|
128
|
+
---
|
117
129
|
|
118
130
|
## 🧪 Testing Tip
|
119
131
|
|
120
|
-
You can test the generated Flask app directly with `app.test_client()` if
|
132
|
+
You can test the generated Flask app directly with `app.test_client()` if using the programmatic interface:
|
121
133
|
|
122
134
|
```python
|
123
135
|
from bisslog_flask import BisslogFlask
|
124
136
|
|
125
137
|
def test_user_create():
|
126
|
-
app = BisslogFlask(
|
138
|
+
app = BisslogFlask(
|
139
|
+
metadata_file="metadata.yml",
|
140
|
+
use_cases_folder_path="src/use_cases"
|
141
|
+
)
|
127
142
|
client = app.test_client()
|
128
143
|
response = client.post("/user", json={"name": "Ana", "email": "ana@example.com"})
|
129
144
|
assert response.status_code == 200
|
130
145
|
```
|
131
146
|
|
132
|
-
|
133
|
-
|
147
|
+
If you're generating the code (boilerplate), you just need to test your use cases.
|
134
148
|
|
149
|
+
---
|
135
150
|
|
136
151
|
## 📜 License
|
137
152
|
|
138
|
-
This project is licensed under the MIT License.
|
139
|
-
|
140
|
-
|
141
|
-
|
153
|
+
This project is licensed under the MIT License.
|
154
|
+
See the [LICENSE](LICENSE) file for details.
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# bisslog-flask
|
2
|
+
|
3
|
+
[](https://pypi.org/project/bisslog-flask/)
|
4
|
+
[](LICENSE)
|
5
|
+
|
6
|
+
**bisslog-flask** is an extension of the Bisslog library to support processes with Flask.
|
7
|
+
It enables dynamic HTTP and WebSocket route registration from use case metadata, allowing developers to build clean, modular, and metadata-driven APIs with minimal boilerplate.
|
8
|
+
|
9
|
+
Part of the Bisslog ecosystem, it is designed to work seamlessly with domain-centric architectures like Hexagonal or Clean Architecture.
|
10
|
+
|
11
|
+
---
|
12
|
+
|
13
|
+
## ✨ Features
|
14
|
+
|
15
|
+
- 🔁 Dynamic route registration for HTTP and WebSocket triggers
|
16
|
+
- 🧠 Metadata-driven setup – use YAML or JSON to declare your use cases
|
17
|
+
- 🔒 Automatic CORS per endpoint using `flask-cors`
|
18
|
+
- 🔌 Extensible resolver pattern – plug in your own processor
|
19
|
+
- ⚙️ Mapper integration – maps HTTP request parts to domain function arguments
|
20
|
+
|
21
|
+
---
|
22
|
+
|
23
|
+
## 📦 Installation
|
24
|
+
|
25
|
+
```bash
|
26
|
+
pip install bisslog-flask
|
27
|
+
```
|
28
|
+
|
29
|
+
---
|
30
|
+
|
31
|
+
## 🚀 Quickstart
|
32
|
+
|
33
|
+
### Programmatically
|
34
|
+
|
35
|
+
Use this approach if you want to configure the app before Bisslog touches it:
|
36
|
+
|
37
|
+
```python
|
38
|
+
from flask import Flask
|
39
|
+
from bisslog_flask import BisslogFlask
|
40
|
+
|
41
|
+
app = Flask(__name__)
|
42
|
+
BisslogFlask(
|
43
|
+
metadata_file="metadata.yml",
|
44
|
+
use_cases_folder_path="src/domain/use_cases",
|
45
|
+
app=app
|
46
|
+
)
|
47
|
+
|
48
|
+
if __name__ == "__main__":
|
49
|
+
app.run(debug=True)
|
50
|
+
```
|
51
|
+
|
52
|
+
Or use the factory version:
|
53
|
+
|
54
|
+
```python
|
55
|
+
from bisslog_flask import BisslogFlask
|
56
|
+
|
57
|
+
app = BisslogFlask(
|
58
|
+
metadata_file="metadata.yml",
|
59
|
+
use_cases_folder_path="src/domain/use_cases"
|
60
|
+
)
|
61
|
+
|
62
|
+
if __name__ == "__main__":
|
63
|
+
app.run(debug=True)
|
64
|
+
```
|
65
|
+
|
66
|
+
---
|
67
|
+
|
68
|
+
## 🖥️ CLI Usage
|
69
|
+
|
70
|
+
You can also use the `bisslog_flask` CLI to run or generate a Flask app.
|
71
|
+
|
72
|
+
```bash
|
73
|
+
bisslog_flask run [--metadata-file FILE] [--use-cases-folder-path DIR]
|
74
|
+
[--infra-folder-path DIR] [--encoding ENC]
|
75
|
+
[--secret-key KEY] [--jwt-secret-key KEY]
|
76
|
+
|
77
|
+
bisslog_flask build [--metadata-file FILE] [--use-cases-folder-path DIR]
|
78
|
+
[--infra-folder-path DIR] [--encoding ENC]
|
79
|
+
[--target-filename FILE]
|
80
|
+
```
|
81
|
+
|
82
|
+
- `run`: Launches the Flask application from metadata.
|
83
|
+
- `build`: Generates a boilerplate Flask file (`flask_app.py` by default).
|
84
|
+
|
85
|
+
All options are optional. You can override defaults via CLI flags.
|
86
|
+
|
87
|
+
---
|
88
|
+
|
89
|
+
## 🔐 CORS Handling
|
90
|
+
|
91
|
+
CORS is applied only when `allow_cors: true` is specified in the trigger.
|
92
|
+
|
93
|
+
Fully dynamic: works even with Flask dynamic routes like `/users/<id>`.
|
94
|
+
|
95
|
+
Powered by `@cross_origin` from `flask-cors`.
|
96
|
+
|
97
|
+
---
|
98
|
+
|
99
|
+
## ✅ Requirements
|
100
|
+
|
101
|
+
- Python ≥ 3.7
|
102
|
+
- Flask ≥ 2.0
|
103
|
+
- bisslog-schema ≥ 0.0.3
|
104
|
+
- flask-cors
|
105
|
+
- (Optional) flask-sock if using WebSocket triggers
|
106
|
+
|
107
|
+
---
|
108
|
+
|
109
|
+
## 🧪 Testing Tip
|
110
|
+
|
111
|
+
You can test the generated Flask app directly with `app.test_client()` if using the programmatic interface:
|
112
|
+
|
113
|
+
```python
|
114
|
+
from bisslog_flask import BisslogFlask
|
115
|
+
|
116
|
+
def test_user_create():
|
117
|
+
app = BisslogFlask(
|
118
|
+
metadata_file="metadata.yml",
|
119
|
+
use_cases_folder_path="src/use_cases"
|
120
|
+
)
|
121
|
+
client = app.test_client()
|
122
|
+
response = client.post("/user", json={"name": "Ana", "email": "ana@example.com"})
|
123
|
+
assert response.status_code == 200
|
124
|
+
```
|
125
|
+
|
126
|
+
If you're generating the code (boilerplate), you just need to test your use cases.
|
127
|
+
|
128
|
+
---
|
129
|
+
|
130
|
+
## 📜 License
|
131
|
+
|
132
|
+
This project is licensed under the MIT License.
|
133
|
+
See the [LICENSE](LICENSE) file for details.
|
@@ -0,0 +1,353 @@
|
|
1
|
+
"""
|
2
|
+
Module for generating a Flask application boilerplate from Bisslog metadata and use case code.
|
3
|
+
|
4
|
+
This builder analyzes declared metadata (e.g., triggers) and discovered use case implementations,
|
5
|
+
and generates the corresponding Flask code—including HTTP routes, WebSocket endpoints, security
|
6
|
+
configuration, and runtime setup.
|
7
|
+
|
8
|
+
The generated code is returned as a full Python script and can be written to a file (e.g.,
|
9
|
+
`flask_app.py`).
|
10
|
+
"""
|
11
|
+
from typing import Optional, Callable
|
12
|
+
import json
|
13
|
+
|
14
|
+
from bisslog_schema import read_full_service_metadata
|
15
|
+
from bisslog_schema.eager_import_module_or_package import EagerImportModulePackage
|
16
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerWebsocket
|
17
|
+
from bisslog_schema.setup import get_setup_metadata
|
18
|
+
from bisslog_schema.use_case_code_inspector.use_case_code_metadata import UseCaseCodeInfo, \
|
19
|
+
UseCaseCodeInfoClass, UseCaseCodeInfoObject
|
20
|
+
from .static_python_construct_data import StaticPythonConstructData
|
21
|
+
|
22
|
+
|
23
|
+
class BuilderFlaskAppManager:
|
24
|
+
"""
|
25
|
+
Flask application builder for Bisslog-based services.
|
26
|
+
|
27
|
+
This class dynamically generates Flask code based on user-declared metadata and
|
28
|
+
the implementation of use cases discovered in the source tree. It supports HTTP
|
29
|
+
and WebSocket triggers, integrates runtime setup from decorators, and configures
|
30
|
+
environment-based security.
|
31
|
+
|
32
|
+
The result is a complete Flask application scaffold that can be directly executed
|
33
|
+
or used as a starting point for further customization.
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self, eager_importer: Callable[[str], None]):
|
37
|
+
self._eager_importer = eager_importer
|
38
|
+
|
39
|
+
|
40
|
+
def _get_bisslog_setup(self, infra_path: Optional[str]) -> Optional[StaticPythonConstructData]:
|
41
|
+
"""
|
42
|
+
Retrieves the Bisslog setup call for the 'flask' runtime, if defined.
|
43
|
+
|
44
|
+
This inspects the global Bisslog configuration and returns the corresponding
|
45
|
+
setup function call code for Flask.
|
46
|
+
|
47
|
+
Returns
|
48
|
+
-------
|
49
|
+
Optional[StaticPythonConstructData]
|
50
|
+
The setup code and imports, or None if no setup was declared.
|
51
|
+
"""
|
52
|
+
self._eager_importer(infra_path)
|
53
|
+
setup_metadata = get_setup_metadata()
|
54
|
+
if setup_metadata is None:
|
55
|
+
return None
|
56
|
+
|
57
|
+
if setup_metadata.setup_function is not None:
|
58
|
+
n_params = setup_metadata.setup_function.n_params
|
59
|
+
if n_params == 0:
|
60
|
+
build = f"{setup_metadata.setup_function.function_name}()"
|
61
|
+
elif n_params == 1:
|
62
|
+
build = f"{setup_metadata.setup_function.function_name}(\"flask\")"
|
63
|
+
else:
|
64
|
+
build = (f"{setup_metadata.setup_function.function_name}(\"flask\")"
|
65
|
+
" # TODO: change this")
|
66
|
+
return StaticPythonConstructData(
|
67
|
+
importing={setup_metadata.setup_function.module:
|
68
|
+
{setup_metadata.setup_function.function_name}},
|
69
|
+
build=build,
|
70
|
+
)
|
71
|
+
custom_runtime_setup = setup_metadata.runtime.get("flask", None)
|
72
|
+
if custom_runtime_setup is not None:
|
73
|
+
return StaticPythonConstructData(
|
74
|
+
importing={custom_runtime_setup.module:
|
75
|
+
{custom_runtime_setup.function_name}},
|
76
|
+
build=f"{custom_runtime_setup.function_name}()"
|
77
|
+
)
|
78
|
+
return None
|
79
|
+
|
80
|
+
@staticmethod
|
81
|
+
def _generate_security_code() -> StaticPythonConstructData:
|
82
|
+
"""
|
83
|
+
Generates Flask configuration code for secret keys using environment variables.
|
84
|
+
|
85
|
+
Returns
|
86
|
+
-------
|
87
|
+
StaticPythonConstructData
|
88
|
+
Code that assigns `SECRET_KEY` and `JWT_SECRET_KEY` to the Flask app.
|
89
|
+
"""
|
90
|
+
build = """
|
91
|
+
if "SECRET_KEY" in os.environ:
|
92
|
+
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
|
93
|
+
if "JWT_SECRET_KEY" in os.environ:
|
94
|
+
app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"]
|
95
|
+
"""
|
96
|
+
return StaticPythonConstructData(build=build)
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def _generate_use_case_code_build(use_case_code_info: UseCaseCodeInfo):
|
100
|
+
"""
|
101
|
+
Prepares the use case callable to be used in HTTP or WebSocket routes.
|
102
|
+
|
103
|
+
If the use case is a class, an instance is created. If it's an object, it's referenced.
|
104
|
+
|
105
|
+
Parameters
|
106
|
+
----------
|
107
|
+
use_case_code_info : UseCaseCodeInfo
|
108
|
+
Static metadata about the use case implementation.
|
109
|
+
|
110
|
+
Returns
|
111
|
+
-------
|
112
|
+
Tuple[str, StaticPythonConstructData]
|
113
|
+
- Name of the callable reference (e.g., variable or instance).
|
114
|
+
- Generated setup code and required imports.
|
115
|
+
"""
|
116
|
+
importing = {"flask": {"request"}, "bisslog.utils.mapping": {"Mapper"}}
|
117
|
+
starting_build = ""
|
118
|
+
if isinstance(use_case_code_info, UseCaseCodeInfoClass):
|
119
|
+
importing[use_case_code_info.module] = {use_case_code_info.class_name}
|
120
|
+
uc_callable = f"{use_case_code_info.name}_uc"
|
121
|
+
starting_build += f"{uc_callable} = {use_case_code_info.class_name}()"
|
122
|
+
elif isinstance(use_case_code_info, UseCaseCodeInfoObject):
|
123
|
+
importing[use_case_code_info.module] = {use_case_code_info.var_name}
|
124
|
+
uc_callable = use_case_code_info.var_name
|
125
|
+
else:
|
126
|
+
raise ValueError("Unsupported UseCaseCodeInfo type")
|
127
|
+
return uc_callable, StaticPythonConstructData(build=starting_build, importing=importing)
|
128
|
+
|
129
|
+
@staticmethod
|
130
|
+
def _generate_use_case_code_http_trigger(
|
131
|
+
use_case_key: str, uc_callable: str, use_case_code_info: UseCaseCodeInfo,
|
132
|
+
trigger_info: TriggerHttp, identifier: int) -> StaticPythonConstructData:
|
133
|
+
"""
|
134
|
+
Generates the code for a use case with an HTTP trigger.
|
135
|
+
|
136
|
+
Parameters
|
137
|
+
----------
|
138
|
+
use_case_key : str
|
139
|
+
Name used to identify the use case route.
|
140
|
+
use_case_code_info : UseCaseCodeInfo
|
141
|
+
Static code metadata for the specific use case.
|
142
|
+
trigger_info : TriggerHttp
|
143
|
+
Metadata of the HTTP trigger.
|
144
|
+
|
145
|
+
Returns
|
146
|
+
-------
|
147
|
+
StaticPythonConstructData
|
148
|
+
The generated code for the HTTP trigger.
|
149
|
+
"""
|
150
|
+
starting_build = ""
|
151
|
+
mapper_code_lines = []
|
152
|
+
if trigger_info.mapper is not None:
|
153
|
+
mapper_name = f"{use_case_code_info.name}_mapper_{identifier}"
|
154
|
+
starting_build += (f"\n{mapper_name} = Mapper(name=\"{use_case_key}_mapper\", "
|
155
|
+
f"base={json.dumps(trigger_info.mapper)})")
|
156
|
+
mapper_code_lines.append(f"""
|
157
|
+
res_map = {mapper_name}.map({{
|
158
|
+
"path_query": request.view_args or {{}},
|
159
|
+
"body": request.get_json(silent=True) or {{}},
|
160
|
+
"params": request.args.to_dict(),
|
161
|
+
"headers": request.headers,
|
162
|
+
}})""")
|
163
|
+
method = trigger_info.method.upper()
|
164
|
+
flask_path = (trigger_info.path or f"/{use_case_key}").replace("{", "<").replace("}", ">")
|
165
|
+
handler_name = f"{use_case_key}_handler_{identifier}"
|
166
|
+
|
167
|
+
lines = [
|
168
|
+
f'@app.route("{flask_path}", methods=["{method}"])',
|
169
|
+
f'def {handler_name}():',
|
170
|
+
]
|
171
|
+
if not mapper_code_lines:
|
172
|
+
lines.append(" kwargs = {}")
|
173
|
+
lines.append(" kwargs.update(request.view_args or {})")
|
174
|
+
lines.append(" kwargs.update(request.get_json(silent=True) or {})")
|
175
|
+
lines.append(" kwargs.update(request.args.to_dict())")
|
176
|
+
lines.append(" kwargs.update(dict(request.headers))")
|
177
|
+
lines.append(f" return {uc_callable}(**kwargs)\n")
|
178
|
+
else:
|
179
|
+
lines.extend(mapper_code_lines)
|
180
|
+
lines.append(f' return {uc_callable}(**res_map)\n')
|
181
|
+
|
182
|
+
return StaticPythonConstructData(build=starting_build, body="\n".join(lines))
|
183
|
+
|
184
|
+
@staticmethod
|
185
|
+
def _generate_use_case_code_websocket_trigger(
|
186
|
+
use_case_key: str,
|
187
|
+
uc_callable: str,
|
188
|
+
use_case_code_info: UseCaseCodeInfo,
|
189
|
+
trigger_info: TriggerWebsocket,
|
190
|
+
identifier: int
|
191
|
+
) -> StaticPythonConstructData:
|
192
|
+
"""
|
193
|
+
Generates the code for a use case with a WebSocket trigger using flask-sock.
|
194
|
+
|
195
|
+
Parameters
|
196
|
+
----------
|
197
|
+
use_case_key : str
|
198
|
+
The identifier of the use case.
|
199
|
+
uc_callable : str
|
200
|
+
The callable name to invoke.
|
201
|
+
use_case_code_info : UseCaseCodeInfo
|
202
|
+
Info about where the use case is defined.
|
203
|
+
trigger_info : TriggerWebsocket
|
204
|
+
Metadata describing the trigger.
|
205
|
+
identifier : int
|
206
|
+
An integer used to ensure uniqueness of function names.
|
207
|
+
|
208
|
+
Returns
|
209
|
+
-------
|
210
|
+
StaticPythonConstructData
|
211
|
+
Code and imports needed for WebSocket registration.
|
212
|
+
"""
|
213
|
+
route_key = trigger_info.route_key or f"{use_case_key}.default"
|
214
|
+
handler_name = f"{use_case_key}_ws_handler_{identifier}"
|
215
|
+
mapper_decl = ""
|
216
|
+
|
217
|
+
imports = {
|
218
|
+
use_case_code_info.module: {use_case_code_info.name},
|
219
|
+
"flask_sock": {"Sock"},
|
220
|
+
"flask": {"request"},
|
221
|
+
"bisslog.utils.mapping": {"Mapper"},
|
222
|
+
"json": None
|
223
|
+
}
|
224
|
+
|
225
|
+
if trigger_info.mapper:
|
226
|
+
mapper_var = f"{use_case_key}_ws_mapper_{identifier}"
|
227
|
+
mapper_json = json.dumps(trigger_info.mapper)
|
228
|
+
mapper_decl = (f'\n{mapper_var} = Mapper(name="{use_case_key}_ws_mapper",'
|
229
|
+
f' base={mapper_json})')
|
230
|
+
|
231
|
+
mapper_code = f"""
|
232
|
+
try:
|
233
|
+
body = json.loads(data)
|
234
|
+
except Exception:
|
235
|
+
body = {{}}
|
236
|
+
res_map = {mapper_var}.map({{
|
237
|
+
"route_key": "{route_key}",
|
238
|
+
"connection_id": request.headers.get("Sec-WebSocket-Key"),
|
239
|
+
"headers": request.headers,
|
240
|
+
"body": body
|
241
|
+
}})
|
242
|
+
response = {uc_callable}(**res_map)
|
243
|
+
"""
|
244
|
+
|
245
|
+
else:
|
246
|
+
# fallback: pass entire raw message
|
247
|
+
mapper_code = f"""
|
248
|
+
try:
|
249
|
+
payload = json.loads(data)
|
250
|
+
except Exception:
|
251
|
+
payload = {{}}
|
252
|
+
response = {uc_callable}(**payload)
|
253
|
+
"""
|
254
|
+
|
255
|
+
build = f"""
|
256
|
+
@sock.route("/ws/{route_key}")
|
257
|
+
def {handler_name}(ws):
|
258
|
+
while True:
|
259
|
+
data = ws.receive()
|
260
|
+
if data is None:
|
261
|
+
break; # Client disconnected
|
262
|
+
{mapper_code}
|
263
|
+
if response is not None:
|
264
|
+
ws.send(response)
|
265
|
+
"""
|
266
|
+
|
267
|
+
return StaticPythonConstructData(
|
268
|
+
importing=imports,
|
269
|
+
build=(mapper_decl + build)
|
270
|
+
)
|
271
|
+
|
272
|
+
def __call__(self,
|
273
|
+
metadata_file: Optional[str] = None,
|
274
|
+
use_cases_folder_path: Optional[str] = None,
|
275
|
+
infra_path: Optional[str] = None,
|
276
|
+
*,
|
277
|
+
encoding: str = "utf-8",
|
278
|
+
secret_key: Optional[str] = None,
|
279
|
+
jwt_secret_key: Optional[str] = None,
|
280
|
+
**kwargs) -> str:
|
281
|
+
"""
|
282
|
+
Main entry point for generating the full Flask application code.
|
283
|
+
|
284
|
+
This method orchestrates metadata loading, trigger processing, and Flask code generation
|
285
|
+
(HTTP routes, WebSocket handlers, runtime setup, security config). The resulting app code
|
286
|
+
is returned as a ready-to-write Python string.
|
287
|
+
|
288
|
+
Parameters
|
289
|
+
----------
|
290
|
+
metadata_file : str, optional
|
291
|
+
Path to the YAML or JSON metadata file.
|
292
|
+
use_cases_folder_path : str, optional
|
293
|
+
Path to the folder where use case implementations are located.
|
294
|
+
infra_path : str, optional
|
295
|
+
Path to additional infrastructure or adapter code.
|
296
|
+
encoding : str, default="utf-8"
|
297
|
+
Encoding used to read the metadata file.
|
298
|
+
secret_key : str, optional
|
299
|
+
secret key for Flask configuration.
|
300
|
+
jwt_secret_key : str, optional
|
301
|
+
JWT secret key for Flask configuration.
|
302
|
+
**kwargs
|
303
|
+
Additional keyword arguments (currently unused).
|
304
|
+
|
305
|
+
Returns
|
306
|
+
-------
|
307
|
+
str
|
308
|
+
The complete Flask application source code as a string.
|
309
|
+
"""
|
310
|
+
full_service_metadata = read_full_service_metadata(
|
311
|
+
metadata_file=metadata_file,
|
312
|
+
use_cases_folder_path=use_cases_folder_path,
|
313
|
+
encoding=encoding
|
314
|
+
)
|
315
|
+
service_info = full_service_metadata.declared_metadata
|
316
|
+
use_cases = full_service_metadata.discovered_use_cases
|
317
|
+
|
318
|
+
res = StaticPythonConstructData(
|
319
|
+
importing={"flask": {"Flask"}, "os": None},
|
320
|
+
build="app = Flask(__name__)"
|
321
|
+
)
|
322
|
+
res += self._get_bisslog_setup(infra_path)
|
323
|
+
|
324
|
+
res += self._generate_security_code()
|
325
|
+
|
326
|
+
# Use cases
|
327
|
+
for use_case_key in service_info.use_cases:
|
328
|
+
use_case_info: UseCaseInfo = service_info.use_cases[use_case_key]
|
329
|
+
use_case_code_info: UseCaseCodeInfo = use_cases[use_case_key]
|
330
|
+
triggers_http = [t for t in use_case_info.triggers
|
331
|
+
if isinstance(t.options, TriggerHttp)]
|
332
|
+
triggers_ws = [t for t in use_case_info.triggers
|
333
|
+
if isinstance(t.options, TriggerWebsocket)]
|
334
|
+
triggers_flask = triggers_http + triggers_ws
|
335
|
+
if len(triggers_flask) == 0:
|
336
|
+
continue
|
337
|
+
uc_callable, res_uc = self._generate_use_case_code_build(use_case_code_info)
|
338
|
+
res += res_uc
|
339
|
+
for i, trigger in enumerate(triggers_flask):
|
340
|
+
if isinstance(trigger.options, TriggerHttp):
|
341
|
+
res += self._generate_use_case_code_http_trigger(
|
342
|
+
use_case_key, uc_callable, use_case_code_info, trigger.options, i
|
343
|
+
)
|
344
|
+
elif isinstance(trigger.options, TriggerWebsocket):
|
345
|
+
res += self._generate_use_case_code_websocket_trigger(
|
346
|
+
use_case_key, uc_callable, use_case_code_info, trigger.options, i
|
347
|
+
)
|
348
|
+
res += StaticPythonConstructData(body='\nif __name__ == "__main__":\n'
|
349
|
+
' app.run(debug=True, host="0.0.0.0")')
|
350
|
+
return res.generate_boiler_plate_flask()
|
351
|
+
|
352
|
+
|
353
|
+
bisslog_flask_builder = BuilderFlaskAppManager(EagerImportModulePackage(("src.infra", "infra")))
|