bisslog-flask 0.0.1__py3-none-any.whl
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/__init__.py +26 -0
- bisslog_flask/initializer/__init__.py +0 -0
- bisslog_flask/initializer/bisslog_flask_http_resolver.py +186 -0
- bisslog_flask/initializer/bisslog_flask_resolver.py +46 -0
- bisslog_flask/initializer/bisslog_flask_ws_resolver.py +76 -0
- bisslog_flask/initializer/init_flask_app_manager.py +123 -0
- bisslog_flask/socket_helper/__init__.py +0 -0
- bisslog_flask/socket_helper/socket_helper.py +106 -0
- bisslog_flask-0.0.1.dist-info/METADATA +141 -0
- bisslog_flask-0.0.1.dist-info/RECORD +13 -0
- bisslog_flask-0.0.1.dist-info/WHEEL +5 -0
- bisslog_flask-0.0.1.dist-info/licenses/LICENSE +21 -0
- bisslog_flask-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
bisslog_flask
|
3
|
+
|
4
|
+
An extension of the `bisslog` library to support service orchestration via Flask.
|
5
|
+
|
6
|
+
This package enables dynamic registration of HTTP and WebSocket routes in a Flask
|
7
|
+
application, using declarative metadata (YAML/JSON). It promotes clean separation
|
8
|
+
between application logic and infrastructure concerns, aligning with hexagonal
|
9
|
+
or clean architecture principles.
|
10
|
+
|
11
|
+
|
12
|
+
Requirements
|
13
|
+
------------
|
14
|
+
- Flask >= 2.0
|
15
|
+
- bisslog-schema >= 0.0.3
|
16
|
+
- flask-cors
|
17
|
+
- (optional) flask-socketio for WebSocket integration
|
18
|
+
|
19
|
+
License
|
20
|
+
-------
|
21
|
+
MIT ยฉ Darwin Stiven Herrera Cartagena
|
22
|
+
"""
|
23
|
+
from .initializer.init_flask_app_manager import BisslogFlask
|
24
|
+
from .socket_helper.socket_helper import BisslogFlaskSocketHelper
|
25
|
+
|
26
|
+
__all__ = ["BisslogFlask", "BisslogFlaskSocketHelper"]
|
File without changes
|
@@ -0,0 +1,186 @@
|
|
1
|
+
"""
|
2
|
+
Flask HTTP resolver for Bisslog-based use case routing.
|
3
|
+
|
4
|
+
This module provides a class to dynamically register HTTP endpoints
|
5
|
+
in a Flask application using 'use case metadata'. It supports CORS configuration
|
6
|
+
and flexible request mapping using the Bisslog schema and Mapper.
|
7
|
+
|
8
|
+
Classes
|
9
|
+
-------
|
10
|
+
BisslogFlaskHttpResolver : Register HTTP routes for use cases based on metadata.
|
11
|
+
|
12
|
+
Dependencies
|
13
|
+
------------
|
14
|
+
- Flask
|
15
|
+
- flask_cors
|
16
|
+
- bisslog_schema
|
17
|
+
- bisslog.utils.mapping
|
18
|
+
"""
|
19
|
+
from copy import deepcopy
|
20
|
+
from typing import Callable, Optional, Dict
|
21
|
+
|
22
|
+
from flask import Flask, request, jsonify
|
23
|
+
try:
|
24
|
+
from flask_cors import cross_origin
|
25
|
+
except ImportError:
|
26
|
+
cross_origin = None
|
27
|
+
from bisslog.utils.mapping import Mapper
|
28
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerInfo
|
29
|
+
from bisslog_schema.schema.triggers.trigger_mappable import TriggerMappable
|
30
|
+
|
31
|
+
from .bisslog_flask_resolver import BisslogFlaskResolver
|
32
|
+
|
33
|
+
|
34
|
+
class BisslogFlaskHttpResolver(BisslogFlaskResolver):
|
35
|
+
"""
|
36
|
+
Flask HTTP resolver that dynamically registers use cases as routes based on metadata.
|
37
|
+
|
38
|
+
This resolver wraps use case callables and maps request data accordingly,
|
39
|
+
applying optional CORS configuration at the endpoint level.
|
40
|
+
|
41
|
+
Inherits
|
42
|
+
--------
|
43
|
+
BisslogFlaskResolver
|
44
|
+
Base resolver interface for trigger-based route binding.
|
45
|
+
"""
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def _lambda_fn(*args, fn, __mapper__: Optional[Mapper], **kwargs):
|
49
|
+
"""Wraps a use case function to extract and map request data.
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
*args : tuple
|
54
|
+
Positional arguments passed by Flask (usually none).
|
55
|
+
fn : Callable
|
56
|
+
The actual use case function to invoke.
|
57
|
+
__mapper__ : Optional[Mapper]
|
58
|
+
Mapper to transform HTTP request parts into function arguments.
|
59
|
+
**kwargs : dict
|
60
|
+
Keyword arguments passed by Flask (URL params, etc.).
|
61
|
+
|
62
|
+
Returns
|
63
|
+
-------
|
64
|
+
flask.Response
|
65
|
+
A JSON response with the result of the use case.
|
66
|
+
"""
|
67
|
+
if __mapper__ is None:
|
68
|
+
more_kwargs = {}
|
69
|
+
if request.method.lower() not in ["get"]:
|
70
|
+
more_kwargs.update(request.get_json(silent=True) or {})
|
71
|
+
return jsonify(fn(*args, **kwargs, **more_kwargs))
|
72
|
+
|
73
|
+
res_map = __mapper__.map({
|
74
|
+
"path_query": request.view_args or {},
|
75
|
+
"body": request.get_json(silent=True) or {},
|
76
|
+
"params": request.args.to_dict(),
|
77
|
+
"headers": request.headers,
|
78
|
+
})
|
79
|
+
res = fn(**res_map)
|
80
|
+
|
81
|
+
return jsonify(res)
|
82
|
+
|
83
|
+
@staticmethod
|
84
|
+
def _use_case_factory(
|
85
|
+
use_case_name: str,
|
86
|
+
fn: Callable,
|
87
|
+
mapper: Optional[Dict[str, str]] = None,
|
88
|
+
trigger: Optional[TriggerHttp] = None
|
89
|
+
):
|
90
|
+
"""
|
91
|
+
Factory to produce a Flask view function with optional mapping and CORS.
|
92
|
+
|
93
|
+
Parameters
|
94
|
+
----------
|
95
|
+
use_case_name : str
|
96
|
+
Unique name of the use case.
|
97
|
+
fn : Callable
|
98
|
+
The function to be wrapped and exposed via HTTP.
|
99
|
+
mapper : dict, optional
|
100
|
+
Mapping schema for request fields, if applicable.
|
101
|
+
trigger : TriggerHttp, optional
|
102
|
+
Trigger options used to configure CORS.
|
103
|
+
|
104
|
+
Returns
|
105
|
+
-------
|
106
|
+
Callable
|
107
|
+
A Flask-compatible view function.
|
108
|
+
"""
|
109
|
+
use_case_fn_copy = deepcopy(fn)
|
110
|
+
__mapper__ = Mapper(name=f"Mapper {use_case_name}", base=mapper) if mapper else None
|
111
|
+
|
112
|
+
def uc(*args, **kwargs):
|
113
|
+
return BisslogFlaskHttpResolver._lambda_fn(
|
114
|
+
*args, fn=use_case_fn_copy, __mapper__=__mapper__, **kwargs
|
115
|
+
)
|
116
|
+
|
117
|
+
# Apply CORS dynamically if allowed
|
118
|
+
if trigger and trigger.allow_cors:
|
119
|
+
if cross_origin is None:
|
120
|
+
raise ImportError("flask_cors is not installed, please install it")
|
121
|
+
cors_kwargs = {
|
122
|
+
"origins": trigger.allowed_origins or "*",
|
123
|
+
"methods": [trigger.method.upper()],
|
124
|
+
"allow_headers": ["Content-Type", "Authorization"],
|
125
|
+
"supports_credentials": True
|
126
|
+
}
|
127
|
+
return cross_origin(**cors_kwargs)(uc)
|
128
|
+
|
129
|
+
return uc
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def _add_use_case(cls, app: Flask, use_case_info: UseCaseInfo, trigger: TriggerInfo,
|
133
|
+
use_case_function):
|
134
|
+
"""
|
135
|
+
Adds an HTTP endpoint to the Flask app for a given use case.
|
136
|
+
|
137
|
+
Parameters
|
138
|
+
----------
|
139
|
+
app : Flask
|
140
|
+
The Flask application instance.
|
141
|
+
use_case_info : UseCaseInfo
|
142
|
+
Metadata describing the use case being added.
|
143
|
+
trigger : TriggerInfo
|
144
|
+
Metadata describing the trigger configuration (must be HTTP).
|
145
|
+
use_case_function : Callable
|
146
|
+
The use case logic to expose via HTTP.
|
147
|
+
"""
|
148
|
+
if not isinstance(trigger.options, TriggerHttp):
|
149
|
+
return
|
150
|
+
|
151
|
+
method = trigger.options.method.upper()
|
152
|
+
path = trigger.options.path.replace("{", "<").replace("}", ">")
|
153
|
+
|
154
|
+
mapper = (
|
155
|
+
trigger.options.mapper
|
156
|
+
if isinstance(trigger.options, TriggerMappable)
|
157
|
+
else None
|
158
|
+
)
|
159
|
+
app.add_url_rule(
|
160
|
+
path,
|
161
|
+
endpoint=use_case_info.keyname + " " + path,
|
162
|
+
methods=[method],
|
163
|
+
view_func=cls._use_case_factory(
|
164
|
+
use_case_name=use_case_info.keyname,
|
165
|
+
fn=use_case_function,
|
166
|
+
mapper=mapper,
|
167
|
+
trigger=trigger.options
|
168
|
+
)
|
169
|
+
)
|
170
|
+
|
171
|
+
def __call__(self, app: Flask, use_case_info: UseCaseInfo,
|
172
|
+
trigger_info: TriggerInfo, use_case_callable: Callable, **kwargs):
|
173
|
+
"""Entry point to register a use case route using this resolver.
|
174
|
+
|
175
|
+
Parameters
|
176
|
+
----------
|
177
|
+
app : Flask
|
178
|
+
The Flask app where the route will be registered.
|
179
|
+
use_case_info : UseCaseInfo
|
180
|
+
Metadata of the use case.
|
181
|
+
trigger_info : TriggerInfo
|
182
|
+
Trigger configuration for the route.
|
183
|
+
use_case_callable : Callable
|
184
|
+
The function to execute when the route is called.
|
185
|
+
"""
|
186
|
+
self._add_use_case(app, use_case_info, trigger_info, use_case_callable)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""
|
2
|
+
Abstract base class for Bisslog Flask route resolvers.
|
3
|
+
|
4
|
+
This module defines a common interface for resolving use case routes
|
5
|
+
in a Flask application. Subclasses implement logic to register routes
|
6
|
+
based on different trigger types (e.g., HTTP, WebSocket).
|
7
|
+
"""
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Callable
|
10
|
+
|
11
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerInfo
|
12
|
+
|
13
|
+
from flask import Flask
|
14
|
+
|
15
|
+
|
16
|
+
class BisslogFlaskResolver(ABC):
|
17
|
+
"""Abstract base class for registering use case routes in a Flask application.
|
18
|
+
|
19
|
+
Implementations of this class handle the translation of trigger metadata
|
20
|
+
into concrete route registration logic, such as for HTTP or WebSocket endpoints.
|
21
|
+
|
22
|
+
Subclasses must implement the `__call__` method to perform the actual route binding."""
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
def __call__(self, app: Flask, use_case_info: UseCaseInfo,
|
26
|
+
trigger_info: TriggerInfo, use_case_callable: Callable, **kwargs) -> Callable:
|
27
|
+
"""
|
28
|
+
Registers a use case route in a Flask app based on the given trigger.
|
29
|
+
|
30
|
+
Parameters
|
31
|
+
----------
|
32
|
+
app : Flask
|
33
|
+
The Flask application instance where the route should be registered.
|
34
|
+
use_case_info : UseCaseInfo
|
35
|
+
Metadata describing the use case, including name and key.
|
36
|
+
trigger_info : TriggerInfo
|
37
|
+
Trigger metadata (e.g., HTTP, WebSocket) describing how the route is activated.
|
38
|
+
use_case_callable : Callable
|
39
|
+
The function or class instance that implements the use case logic.
|
40
|
+
|
41
|
+
Raises
|
42
|
+
------
|
43
|
+
NotImplementedError
|
44
|
+
If the method is not implemented by a subclass.
|
45
|
+
"""
|
46
|
+
raise NotImplementedError
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""
|
2
|
+
WebSocket resolver for Bisslog-based Flask applications using Flask-SocketIO.
|
3
|
+
|
4
|
+
This module defines the `BisslogFlaskWebSocketResolver` class, which dynamically registers
|
5
|
+
WebSocket-based use case triggers in a Flask app. The class uses metadata definitions to
|
6
|
+
configure event routes (via `route_key`) and binds them to corresponding use case functions.
|
7
|
+
"""
|
8
|
+
from typing import Callable
|
9
|
+
from flask import Flask, request
|
10
|
+
|
11
|
+
try:
|
12
|
+
from flask_socketio import SocketIO
|
13
|
+
except ImportError:
|
14
|
+
class SocketIO:
|
15
|
+
"""Socket IO simulator while is not installed"""
|
16
|
+
def __init__(self, _, ** __):
|
17
|
+
raise ImportError("flask socketio is not installed, please install it")
|
18
|
+
|
19
|
+
from bisslog.utils.mapping import Mapper
|
20
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerInfo
|
21
|
+
from bisslog_schema.schema.triggers.trigger_websocket import TriggerWebsocket
|
22
|
+
from .bisslog_flask_resolver import BisslogFlaskResolver
|
23
|
+
|
24
|
+
|
25
|
+
class BisslogFlaskWebSocketResolver(BisslogFlaskResolver):
|
26
|
+
"""
|
27
|
+
Resolver that registers WebSocket use cases using Flask-SocketIO.
|
28
|
+
|
29
|
+
This class maps WebSocket events (via route_key) to the use case callables.
|
30
|
+
If a SocketIO instance is not already attached to the Flask app,
|
31
|
+
a new one will be created and stored in `app.extensions["socketio"]`.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __call__(self, app: Flask, use_case_info: UseCaseInfo,
|
35
|
+
trigger_info: TriggerInfo, use_case_callable: Callable, **kwargs):
|
36
|
+
"""
|
37
|
+
Register a WebSocket event handler for a use case.
|
38
|
+
|
39
|
+
Parameters
|
40
|
+
----------
|
41
|
+
app : Flask
|
42
|
+
The Flask app instance.
|
43
|
+
use_case_info : UseCaseInfo
|
44
|
+
Metadata about the use case.
|
45
|
+
trigger_info : TriggerWebsocket
|
46
|
+
Trigger options containing the route_key and mapper.
|
47
|
+
use_case_callable : Callable
|
48
|
+
The actual use case function to call when the event is triggered.
|
49
|
+
kwargs : dict
|
50
|
+
Additional optional arguments (ignored here).
|
51
|
+
"""
|
52
|
+
if not isinstance(trigger_info.options, TriggerWebsocket):
|
53
|
+
return
|
54
|
+
|
55
|
+
# Get or create SocketIO instance
|
56
|
+
socket_io_obj = app.extensions.get("socketio")
|
57
|
+
if socket_io_obj is None:
|
58
|
+
socket_io_obj = SocketIO(app, cors_allowed_origins="*")
|
59
|
+
app.extensions["socketio"] = socket_io_obj
|
60
|
+
|
61
|
+
route_key = trigger_info.options.route_key
|
62
|
+
mapper = Mapper(
|
63
|
+
name=f"mapper-ws-{use_case_info.keyname}-{route_key}",
|
64
|
+
base=trigger_info.options.mapper
|
65
|
+
) if trigger_info.options.mapper else None
|
66
|
+
|
67
|
+
@socket_io_obj.on(route_key)
|
68
|
+
def on_event(data):
|
69
|
+
mapped_data = mapper.map({
|
70
|
+
"route_key": route_key,
|
71
|
+
"connection_id": request.sid,
|
72
|
+
"body": data,
|
73
|
+
"headers": dict(request.headers)
|
74
|
+
}) if mapper else data
|
75
|
+
|
76
|
+
return use_case_callable(**mapped_data) if mapper else use_case_callable(data)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
"""
|
2
|
+
Flask application initializer for Bisslog-based services.
|
3
|
+
|
4
|
+
This module defines a manager that reads service metadata and dynamically registers
|
5
|
+
use case endpoints into a Flask application using resolvers for HTTP and WebSocket triggers.
|
6
|
+
|
7
|
+
Classes
|
8
|
+
-------
|
9
|
+
InitFlaskAppManager : Initializes a Flask app with routes from use case metadata.
|
10
|
+
|
11
|
+
Dependencies
|
12
|
+
------------
|
13
|
+
- Flask
|
14
|
+
- bisslog_schema
|
15
|
+
- BisslogFlaskResolver
|
16
|
+
"""
|
17
|
+
|
18
|
+
from typing import Optional, Callable
|
19
|
+
|
20
|
+
from bisslog_schema import read_service_info_with_code
|
21
|
+
from bisslog_schema.schema import UseCaseInfo, TriggerHttp, TriggerWebsocket
|
22
|
+
from flask import Flask
|
23
|
+
|
24
|
+
from .bisslog_flask_http_resolver import BisslogFlaskHttpResolver
|
25
|
+
from .bisslog_flask_resolver import BisslogFlaskResolver
|
26
|
+
from .bisslog_flask_ws_resolver import BisslogFlaskWebSocketResolver
|
27
|
+
|
28
|
+
|
29
|
+
class InitFlaskAppManager:
|
30
|
+
"""
|
31
|
+
Initializes a Flask app by registering routes from metadata using HTTP and WebSocket resolvers.
|
32
|
+
|
33
|
+
This manager reads metadata and code, then applies the appropriate processor (resolver)
|
34
|
+
to each use case according to its trigger type.
|
35
|
+
|
36
|
+
Parameters
|
37
|
+
----------
|
38
|
+
http_processor : BisslogFlaskResolver
|
39
|
+
Resolver used to handle HTTP-triggered use cases.
|
40
|
+
websocket_processor : BisslogFlaskResolver
|
41
|
+
Resolver used to handle WebSocket-triggered use cases.
|
42
|
+
"""
|
43
|
+
|
44
|
+
def __init__(self, http_processor: BisslogFlaskResolver,
|
45
|
+
websocket_processor: BisslogFlaskResolver) -> None:
|
46
|
+
self._http_processor = http_processor
|
47
|
+
self._websocket_processor = websocket_processor
|
48
|
+
|
49
|
+
def __call__(
|
50
|
+
self,
|
51
|
+
metadata_file: Optional[str] = None,
|
52
|
+
use_cases_folder_path: Optional[str] = None,
|
53
|
+
app: Optional[Flask] = None,
|
54
|
+
*,
|
55
|
+
encoding: str = "utf-8",
|
56
|
+
secret_key: Optional[str] = None,
|
57
|
+
jwt_secret_key: Optional[str] = None,
|
58
|
+
**kwargs) -> Flask:
|
59
|
+
"""
|
60
|
+
Loads metadata, discovers use case functions, registers routes and returns the Flask app.
|
61
|
+
|
62
|
+
This method reads metadata and code from the given paths, initializes the Flask app
|
63
|
+
(if not provided), configures security options, and applies HTTP or WebSocket processors
|
64
|
+
based on the trigger type for each use case.
|
65
|
+
|
66
|
+
Parameters
|
67
|
+
----------
|
68
|
+
metadata_file : str, optional
|
69
|
+
Path to the metadata file (YAML/JSON).
|
70
|
+
use_cases_folder_path : str, optional
|
71
|
+
Directory where use case code is located.
|
72
|
+
app : Flask, optional
|
73
|
+
An existing Flask app instance to which routes will be added.
|
74
|
+
If not provided, a new app is created using the service name.
|
75
|
+
encoding : str, optional
|
76
|
+
File encoding for reading metadata (default is "utf-8").
|
77
|
+
secret_key : str, optional
|
78
|
+
Secret key to set in app config (used for session security).
|
79
|
+
jwt_secret_key : str, optional
|
80
|
+
JWT secret key to set in app config (used for token authentication).
|
81
|
+
**kwargs : Any
|
82
|
+
Additional keyword arguments (not currently used).
|
83
|
+
|
84
|
+
Returns
|
85
|
+
-------
|
86
|
+
Flask
|
87
|
+
The Flask app instance with registered use case routes.
|
88
|
+
"""
|
89
|
+
full_service_data = read_service_info_with_code(
|
90
|
+
metadata_file=metadata_file,
|
91
|
+
use_cases_folder_path=use_cases_folder_path,
|
92
|
+
encoding=encoding
|
93
|
+
)
|
94
|
+
service_info = full_service_data.declared_metadata
|
95
|
+
use_cases = full_service_data.discovered_use_cases
|
96
|
+
|
97
|
+
# Initialize Flask app
|
98
|
+
if app is None:
|
99
|
+
app = Flask(service_info.name)
|
100
|
+
|
101
|
+
# Configure security
|
102
|
+
if secret_key is not None:
|
103
|
+
app.config["SECRET_KEY"] = secret_key
|
104
|
+
if jwt_secret_key is not None:
|
105
|
+
app.config["JWT_SECRET_KEY"] = jwt_secret_key
|
106
|
+
|
107
|
+
# Register each use case to the appropriate processor
|
108
|
+
for use_case_keyname in service_info.use_cases:
|
109
|
+
use_case_info: UseCaseInfo = service_info.use_cases[use_case_keyname]
|
110
|
+
use_case_callable: Callable = use_cases[use_case_keyname]
|
111
|
+
|
112
|
+
for trigger in use_case_info.triggers:
|
113
|
+
if isinstance(trigger.options, TriggerHttp):
|
114
|
+
self._http_processor(
|
115
|
+
app, use_case_info, trigger, use_case_callable, **kwargs)
|
116
|
+
if isinstance(trigger.options, TriggerWebsocket):
|
117
|
+
self._websocket_processor(
|
118
|
+
app, use_case_info, trigger, use_case_callable, **kwargs)
|
119
|
+
|
120
|
+
return app
|
121
|
+
|
122
|
+
|
123
|
+
BisslogFlask = InitFlaskAppManager(BisslogFlaskHttpResolver(), BisslogFlaskWebSocketResolver())
|
File without changes
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"""
|
2
|
+
Helper class for sending notifications through WebSocket using Flask-SocketIO.
|
3
|
+
|
4
|
+
This module provides a concrete implementation of the `WebSocketManager` interface,
|
5
|
+
allowing you to send events, manage rooms, and communicate with WebSocket clients
|
6
|
+
via Flask-SocketIO.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from bisslog.ports.ws_manager import WebSocketManager
|
12
|
+
|
13
|
+
|
14
|
+
class BisslogFlaskSocketHelper(WebSocketManager):
|
15
|
+
"""
|
16
|
+
Flask-SocketIO implementation of the WebSocketManager interface.
|
17
|
+
|
18
|
+
This class adapts Flask-SocketIO's functionality to the `bisslog` WebSocket contract,
|
19
|
+
allowing event emission, room management, and direct message delivery using
|
20
|
+
a consistent interface.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, conn) -> None:
|
24
|
+
super().__init__(conn)
|
25
|
+
|
26
|
+
def emit(self, event: str, connection_id: str, payload: Any,
|
27
|
+
broadcast: bool = False, to: str = None):
|
28
|
+
"""
|
29
|
+
Emit an event to a specific connection or to a room.
|
30
|
+
|
31
|
+
Parameters
|
32
|
+
----------
|
33
|
+
event : str
|
34
|
+
Name of the event to emit.
|
35
|
+
connection_id : str
|
36
|
+
SocketIO session ID of the client.
|
37
|
+
payload : Any
|
38
|
+
Data to send.
|
39
|
+
broadcast : bool, optional
|
40
|
+
Whether to broadcast to all clients (default: False).
|
41
|
+
to : str, optional
|
42
|
+
Target room (if broadcasting).
|
43
|
+
"""
|
44
|
+
self.conn.emit(
|
45
|
+
event,
|
46
|
+
payload,
|
47
|
+
to=to or connection_id,
|
48
|
+
broadcast=broadcast
|
49
|
+
)
|
50
|
+
|
51
|
+
def join_room(self, room: str, connection_id: str):
|
52
|
+
"""
|
53
|
+
Add a connection to a room.
|
54
|
+
|
55
|
+
Parameters
|
56
|
+
----------
|
57
|
+
room : str
|
58
|
+
Name of the room.
|
59
|
+
connection_id : str
|
60
|
+
Session ID of the connection to join the room.
|
61
|
+
"""
|
62
|
+
self.conn.server.enter_room(connection_id, room)
|
63
|
+
|
64
|
+
def leave_room(self, room: str, connection_id: str):
|
65
|
+
"""
|
66
|
+
Remove a connection from a room.
|
67
|
+
|
68
|
+
Parameters
|
69
|
+
----------
|
70
|
+
room : str
|
71
|
+
Name of the room.
|
72
|
+
connection_id : str
|
73
|
+
Session ID of the connection to leave the room.
|
74
|
+
"""
|
75
|
+
self.conn.server.leave_room(connection_id, room)
|
76
|
+
|
77
|
+
def rooms(self, connection_id: str):
|
78
|
+
"""
|
79
|
+
Get a list of rooms the connection is part of.
|
80
|
+
|
81
|
+
Parameters
|
82
|
+
----------
|
83
|
+
connection_id : str
|
84
|
+
Session ID of the client.
|
85
|
+
|
86
|
+
Returns
|
87
|
+
-------
|
88
|
+
list[str]
|
89
|
+
List of room names.
|
90
|
+
"""
|
91
|
+
return list(self.conn.server.rooms(connection_id))
|
92
|
+
|
93
|
+
def send(self, event: str, payload: Any = None, connection_id: str = None):
|
94
|
+
"""
|
95
|
+
Send a message with or without an event name.
|
96
|
+
|
97
|
+
Parameters
|
98
|
+
----------
|
99
|
+
event : str
|
100
|
+
Event name or message label.
|
101
|
+
payload : Any, optional
|
102
|
+
Payload to send.
|
103
|
+
connection_id : str, optional
|
104
|
+
Target client (if None, broadcast).
|
105
|
+
"""
|
106
|
+
self.conn.send(payload or event, to=connection_id)
|
@@ -0,0 +1,141 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: bisslog_flask
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: It is an extension of the bisslog library to support processes with flask
|
5
|
+
Author-email: Darwin Stiven Herrera Cartagena <darwinsherrerac@gmail.com>
|
6
|
+
Project-URL: Homepage, https://github.com/darwinhc/bisslog-flask
|
7
|
+
Keywords: hexagonal,adapters,bisslog,flask
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Requires-Python: >=3.7
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: bisslog>=0.0.7
|
14
|
+
Requires-Dist: bisslog-schema>=0.0.3
|
15
|
+
Requires-Dist: flask
|
16
|
+
Provides-Extra: websocket
|
17
|
+
Requires-Dist: flask-socketio; extra == "websocket"
|
18
|
+
Provides-Extra: cors
|
19
|
+
Requires-Dist: flask-cors>=6.0.0; extra == "cors"
|
20
|
+
Dynamic: license-file
|
21
|
+
|
22
|
+
|
23
|
+
# bisslog-flask
|
24
|
+
|
25
|
+
[](https://pypi.org/project/bisslog-flask/)
|
26
|
+
[](LICENSE)
|
27
|
+
|
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.
|
29
|
+
|
30
|
+
Part of the bisslog ecosystem, it is designed to work seamlessly with domain-centric architectures like Hexagonal or Clean Architecture.
|
31
|
+
|
32
|
+
## Features
|
33
|
+
|
34
|
+
- ๐ Dynamic route registration for HTTP and WebSocket triggers
|
35
|
+
|
36
|
+
- ๐ง Metadata-driven setup โ use YAML or JSON to declare your use cases
|
37
|
+
|
38
|
+
- ๐ Automatic CORS per endpoint using flask-cors
|
39
|
+
|
40
|
+
- ๐ Extensible resolver pattern โ plug in your own processor
|
41
|
+
|
42
|
+
- โ๏ธ Mapper integration โ maps HTTP request parts to domain function arguments
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
## ๐ฆ Installation
|
47
|
+
|
48
|
+
~~~shell
|
49
|
+
pip install bisslog-flask
|
50
|
+
~~~
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
## ๐ Quickstart
|
56
|
+
|
57
|
+
### Programmatically
|
58
|
+
|
59
|
+
if you want to configure the app before bisslog touches it
|
60
|
+
~~~python
|
61
|
+
from flask import Flask
|
62
|
+
from bisslog_flask import BisslogFlask
|
63
|
+
|
64
|
+
app = Flask(__name__)
|
65
|
+
BisslogFlask(
|
66
|
+
metadata_file="metadata.yml",
|
67
|
+
use_cases_folder_path="src/domain/use_cases",
|
68
|
+
app=app
|
69
|
+
)
|
70
|
+
|
71
|
+
if __name__ == "__main__":
|
72
|
+
app.run(debug=True)
|
73
|
+
~~~
|
74
|
+
|
75
|
+
or
|
76
|
+
|
77
|
+
~~~python
|
78
|
+
from bisslog_flask import BisslogFlask
|
79
|
+
|
80
|
+
app = BisslogFlask(
|
81
|
+
metadata_file="metadata.yml",
|
82
|
+
use_cases_folder_path="src/domain/use_cases"
|
83
|
+
)
|
84
|
+
|
85
|
+
if __name__ == "__main__":
|
86
|
+
app.run(debug=True)
|
87
|
+
~~~
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
## ๐ง How It Works
|
92
|
+
|
93
|
+
1. Loads metadata and discovers use case functions (or callables), then uses resolvers to register routes dynamically into a Flask app.
|
94
|
+
|
95
|
+
|
96
|
+
## ๐ CORS Handling
|
97
|
+
|
98
|
+
CORS is applied only when allow_cors: true is specified in the trigger
|
99
|
+
|
100
|
+
Fully dynamic: works even with dynamic Flask routes like /users/<id>
|
101
|
+
|
102
|
+
Powered by `@cross_origin` from `flask-cors`
|
103
|
+
|
104
|
+
|
105
|
+
## โ
Requirements
|
106
|
+
|
107
|
+
Python โฅ 3.7
|
108
|
+
|
109
|
+
Flask โฅ 2.0
|
110
|
+
|
111
|
+
bisslog-schema โฅ 0.0.3
|
112
|
+
|
113
|
+
flask-cors
|
114
|
+
|
115
|
+
(Optional) flask-socketio if using WebSocket triggers
|
116
|
+
|
117
|
+
|
118
|
+
## ๐งช Testing Tip
|
119
|
+
|
120
|
+
You can test the generated Flask app directly with `app.test_client()` if you take the programmatic way:
|
121
|
+
|
122
|
+
```python
|
123
|
+
from bisslog_flask import BisslogFlask
|
124
|
+
|
125
|
+
def test_user_create():
|
126
|
+
app = BisslogFlask(metadata_file="metadata.yml", use_cases_folder_path="src/use_cases")
|
127
|
+
client = app.test_client()
|
128
|
+
response = client.post("/user", json={"name": "Ana", "email": "ana@example.com"})
|
129
|
+
assert response.status_code == 200
|
130
|
+
```
|
131
|
+
|
132
|
+
Not generating code or using the programmatic way you just need to test your use cases.
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
## ๐ License
|
137
|
+
|
138
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
139
|
+
|
140
|
+
|
141
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
bisslog_flask/__init__.py,sha256=BEf_UxFtcMfaM-Smh_bwc6Xn8pR8LcEjby3nCs0hdXE,758
|
2
|
+
bisslog_flask/initializer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
bisslog_flask/initializer/bisslog_flask_http_resolver.py,sha256=d-KFQwMXeRyq3_Cu5bxCF7CpnetfnKainChEbdxDk2c,6257
|
4
|
+
bisslog_flask/initializer/bisslog_flask_resolver.py,sha256=QWjft6HZbBplbP1vVMNoPhGoSZBJubAotwLR76HVttw,1708
|
5
|
+
bisslog_flask/initializer/bisslog_flask_ws_resolver.py,sha256=s-eDdk5HA6E_D8iZC6IN7RSW-96mzu8m8VGL3Xqm9PQ,2974
|
6
|
+
bisslog_flask/initializer/init_flask_app_manager.py,sha256=QzO1dae3YFap2VEXK3gSexLkdfBGaLWrHK732z5s0K0,4682
|
7
|
+
bisslog_flask/socket_helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
bisslog_flask/socket_helper/socket_helper.py,sha256=sR9xQSPWzijhPNRh8WRxDbzE7rqFqm4w0Vql4gBGiGU,3037
|
9
|
+
bisslog_flask-0.0.1.dist-info/licenses/LICENSE,sha256=TSlM1hRIXc6yR3xpGzy2DMSSbds0svqHSetfNfQqCEk,1074
|
10
|
+
bisslog_flask-0.0.1.dist-info/METADATA,sha256=w82no5oSrRtS7XEDT38gs6UYIrzyuBHK4eEJkpQptH0,3551
|
11
|
+
bisslog_flask-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
12
|
+
bisslog_flask-0.0.1.dist-info/top_level.txt,sha256=Xk85d0SIhkUP1HjsXOtq2vlU7yQT3mFApyb5IVgtG6w,14
|
13
|
+
bisslog_flask-0.0.1.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Darwin Herrera C.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
bisslog_flask
|