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.
Files changed (48) hide show
  1. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/PKG-INFO +56 -43
  2. bisslog_flask-0.0.2/README.md +133 -0
  3. bisslog_flask-0.0.2/bisslog_flask/builder/builder_flask_app_manager.py +353 -0
  4. bisslog_flask-0.0.2/bisslog_flask/builder/static_python_construct_data.py +172 -0
  5. bisslog_flask-0.0.2/bisslog_flask/cli/__init__.py +88 -0
  6. bisslog_flask-0.0.2/bisslog_flask/cli/commands/build.py +55 -0
  7. bisslog_flask-0.0.2/bisslog_flask/cli/commands/run.py +52 -0
  8. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/init_flask_app_manager.py +16 -2
  9. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/socket_helper/socket_helper.py +0 -3
  10. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/PKG-INFO +56 -43
  11. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/SOURCES.txt +17 -1
  12. bisslog_flask-0.0.2/bisslog_flask.egg-info/entry_points.txt +2 -0
  13. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/requires.txt +1 -1
  14. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/pylintrc +4 -0
  15. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/pyproject.toml +4 -1
  16. bisslog_flask-0.0.2/requirements.txt +3 -0
  17. bisslog_flask-0.0.2/tests/__init__.py +0 -0
  18. bisslog_flask-0.0.2/tests/unit/__init__.py +0 -0
  19. bisslog_flask-0.0.2/tests/unit/builder/__init__.py +0 -0
  20. bisslog_flask-0.0.2/tests/unit/builder/test_builder_flask_app_manager.py +99 -0
  21. bisslog_flask-0.0.2/tests/unit/builder/test_builder_full.py +62 -0
  22. bisslog_flask-0.0.2/tests/unit/builder/test_static_python_res.py +72 -0
  23. bisslog_flask-0.0.2/tests/unit/cli/__init__.py +0 -0
  24. bisslog_flask-0.0.2/tests/unit/cli/test_build.py +31 -0
  25. bisslog_flask-0.0.2/tests/unit/cli/test_cli.py +70 -0
  26. bisslog_flask-0.0.2/tests/unit/cli/test_run.py +31 -0
  27. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/tests/unit/test_init_app_manager.py +11 -4
  28. bisslog_flask-0.0.1/README.md +0 -120
  29. bisslog_flask-0.0.1/requirements.txt +0 -3
  30. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.github/workflows/publish-pypi.yml +0 -0
  31. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.github/workflows/publish-test-pypi.yml +0 -0
  32. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.github/workflows/receive-changes.yml +0 -0
  33. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/.gitignore +0 -0
  34. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/LICENSE +0 -0
  35. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/__init__.py +0 -0
  36. {bisslog_flask-0.0.1/bisslog_flask/initializer → bisslog_flask-0.0.2/bisslog_flask/builder}/__init__.py +0 -0
  37. {bisslog_flask-0.0.1/bisslog_flask/socket_helper → bisslog_flask-0.0.2/bisslog_flask/cli/commands}/__init__.py +0 -0
  38. {bisslog_flask-0.0.1/tests → bisslog_flask-0.0.2/bisslog_flask/initializer}/__init__.py +0 -0
  39. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/bisslog_flask_http_resolver.py +0 -0
  40. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/bisslog_flask_resolver.py +0 -0
  41. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask/initializer/bisslog_flask_ws_resolver.py +0 -0
  42. {bisslog_flask-0.0.1/tests/unit → bisslog_flask-0.0.2/bisslog_flask/socket_helper}/__init__.py +0 -0
  43. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/dependency_links.txt +0 -0
  44. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/bisslog_flask.egg-info/top_level.txt +0 -0
  45. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/requirements/requirements-flask-optional.txt +0 -0
  46. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/setup.cfg +0 -0
  47. {bisslog_flask-0.0.1 → bisslog_flask-0.0.2}/tests/unit/test_http_resolver.py +0 -0
  48. {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.1
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.3
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
  [![PyPI](https://img.shields.io/pypi/v/bisslog-flask)](https://pypi.org/project/bisslog-flask/)
26
25
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
27
26
 
28
- **bisslog-flask** is an extension of the bisslog library to support processes with Flask. 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.
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 bisslog ecosystem, it is designed to work seamlessly with domain-centric architectures like Hexagonal or Clean Architecture.
30
+ Part of the Bisslog ecosystem, it is designed to work seamlessly with domain-centric architectures like Hexagonal or Clean Architecture.
31
31
 
32
- ## Features
32
+ ---
33
33
 
34
- - 🔁 Dynamic route registration for HTTP and WebSocket triggers
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
- ~~~shell
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 bisslog touches it
60
- ~~~python
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
- or
73
+ Or use the factory version:
76
74
 
77
- ~~~python
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
- ## 🔧 How It Works
89
+ ## 🖥️ CLI Usage
92
90
 
93
- 1. Loads metadata and discovers use case functions (or callables), then uses resolvers to register routes dynamically into a Flask app.
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
- ## 🔐 CORS Handling
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
- CORS is applied only when allow_cors: true is specified in the trigger
103
+ - `run`: Launches the Flask application from metadata.
104
+ - `build`: Generates a boilerplate Flask file (`flask_app.py` by default).
99
105
 
100
- Fully dynamic: works even with dynamic Flask routes like /users/<id>
106
+ All options are optional. You can override defaults via CLI flags.
101
107
 
102
- Powered by `@cross_origin` from `flask-cors`
108
+ ---
103
109
 
110
+ ## 🔐 CORS Handling
104
111
 
105
- ## Requirements
112
+ CORS is applied only when `allow_cors: true` is specified in the trigger.
106
113
 
107
- Python 3.7
114
+ Fully dynamic: works even with Flask dynamic routes like `/users/<id>`.
108
115
 
109
- Flask 2.0
116
+ Powered by `@cross_origin` from `flask-cors`.
110
117
 
111
- bisslog-schema ≥ 0.0.3
118
+ ---
112
119
 
113
- flask-cors
120
+ ## ✅ Requirements
114
121
 
115
- (Optional) flask-socketio if using WebSocket triggers
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 you take the programmatic way:
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(metadata_file="metadata.yml", use_cases_folder_path="src/use_cases")
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
- Not generating code or using the programmatic way you just need to test your use cases.
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. See the [LICENSE](LICENSE) file for details.
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
+ [![PyPI](https://img.shields.io/pypi/v/bisslog-flask)](https://pypi.org/project/bisslog-flask/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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")))