diff --git a/app/back-end/gunicorn_config.py b/app/back-end/gunicorn_config.py index 0cb95dd..1e83542 100644 --- a/app/back-end/gunicorn_config.py +++ b/app/back-end/gunicorn_config.py @@ -7,10 +7,14 @@ - Number of worker processes based on the number of CPU cores. Dependencies: -- multiprocessing: Used to determine the number of CPU cores for configuring the number of Gunicorn workers. -- src.setup.env.Env: The class responsible for retrieving environment settings such as host and port. +- multiprocessing: Used to determine the number of CPU cores for configuring the + number of Gunicorn workers. +- src.setup.env.Env: The class responsible for retrieving environment settings such + as host and port. """ +# pylint: disable=invalid-name + import multiprocessing # Bind to specified host and port diff --git a/app/back-end/requirements_dev.txt b/app/back-end/requirements_dev.txt index b3ca909..6666d7a 100644 --- a/app/back-end/requirements_dev.txt +++ b/app/back-end/requirements_dev.txt @@ -1 +1,14 @@ -black \ No newline at end of file +astroid==3.2.4 +black==24.8.0 +click==8.1.7 +dill==0.3.8 +isort==5.13.2 +mccabe==0.7.0 +mypy-extensions==1.0.0 +packaging==24.1 +pathspec==0.12.1 +platformdirs==4.2.2 +pylint==3.2.6 +tomli==2.0.1 +tomlkit==0.13.2 +typing_extensions==4.12.2 diff --git a/app/back-end/run.py b/app/back-end/run.py index 50df4cf..a7352aa 100644 --- a/app/back-end/run.py +++ b/app/back-end/run.py @@ -10,9 +10,12 @@ Dependencies: - src.create_app: The function that sets up and returns the configured Flask app instance. - src.socketio: The Socket.IO instance initialized in the application setup. -- src.setup.env.Env: The class responsible for managing environment variables, including retrieving the host and port settings. +- src.setup.env.Env: The class responsible for managing environment variables, including + retrieving the host and port settings. """ +# pylint: disable=import-error + from src import create_app, socketio, env # Initialize the Flask app diff --git a/app/back-end/src/__init__.py b/app/back-end/src/__init__.py index 94f59d4..910a62a 100644 --- a/app/back-end/src/__init__.py +++ b/app/back-end/src/__init__.py @@ -11,7 +11,8 @@ Dependencies: - Flask: The core web framework. - gevent.monkey: Provides monkey patches to enable cooperative multitasking. -- src.setup.extensions: Contains initialization for Flask extensions like compression, Socket.IO, and CORS. +- src.setup.extensions: Contains initialization for Flask extensions like compression, Socket.IO, + and CORS. - src.setup.router: Defines and manages application routing through blueprints. - src.setup.eventer: Sets up event handlers for application events. - src.constants: Provides constants used in configuration, such as BASE_ROUTE. @@ -20,6 +21,8 @@ Flask: A fully configured Flask application instance with all extensions and routes initialized. """ +# pylint: disable=import-error + import gevent.monkey from flask import Flask diff --git a/app/back-end/src/config.py b/app/back-end/src/config.py index d00557c..a90edb9 100644 --- a/app/back-end/src/config.py +++ b/app/back-end/src/config.py @@ -11,6 +11,8 @@ - dotenv: Used for loading environment variables from a `.env` file. """ +# pylint: disable=import-error + import os from dotenv import load_dotenv diff --git a/app/back-end/src/constants.py b/app/back-end/src/constants.py index 7d5ff2b..c045a08 100644 --- a/app/back-end/src/constants.py +++ b/app/back-end/src/constants.py @@ -7,12 +7,15 @@ - Workspace directory paths, including a template directory. - Base API route and specific routes for various functionalities. -These constants are typically used for file handling, directory management, and routing in the Flask application. +These constants are typically used for file handling, directory management, and routing in the +Flask application. Dependencies: - os: Used for interacting with the operating system to manage file and directory paths. """ +# pylint: disable=import-error + import os diff --git a/app/back-end/src/events/workspace_event.py b/app/back-end/src/events/workspace_event.py index 5685719..da4326f 100644 --- a/app/back-end/src/events/workspace_event.py +++ b/app/back-end/src/events/workspace_event.py @@ -1,8 +1,8 @@ """ This module contains functions for handling workspace-related WebSocket events. -It includes functions that manage file operations within the workspace directory. The module provides real-time -feedback to clients through WebSocket events. +It includes functions that manage file operations within the workspace directory. The module +provides real-time feedback to clients through WebSocket events. Dependencies: - os: Used for file and directory operations. @@ -10,9 +10,12 @@ - src.setup.constants.WORKSPACE_DIR: The base directory for workspace files. """ +# pylint: disable=import-error + import os from src.setup.extensions import socketio +from src.utils.exceptions import UnexpectedError from src.constants import WORKSPACE_DIR @@ -21,18 +24,20 @@ def handle_workspace_file_update(data): """ Handle the 'workspace_file_update' WebSocket event. - This function processes the data received from the client to update a file in the workspace directory. - It ensures that required fields are present, writes the content to the specified file, and handles any errors - by sending appropriate status updates back to the client. + This function processes the data received from the client to update a file in the workspace + directory. It ensures that required fields are present, writes the content to the specified + file, and handles any errors by sending appropriate status updates back to the client. Args: - data (dict): The data received from the client, expected to contain 'uuid', 'fileId', and 'content'. + data (dict): The data received from the client, expected to contain 'uuid', 'fileId', and + 'content'. Emits: - 'workspace_file_update_status': Emits a status message indicating success or failure of the file update operation. + 'workspace_file_update_status': Emits a status message indicating success or failure of + the file update operation. """ uuid = data.get("uuid") - fileId = data.get("fileId") + file_id = data.get("fileId") content = data.get("content") # Ensure the uuid is provided @@ -44,28 +49,26 @@ def handle_workspace_file_update(data): return # Ensure the fileId is provided - if not fileId: + if not file_id: socketio.emit( "workspace_file_update_status", {"status": "error", "message": "File ID is missing"}, ) return - file_path = os.path.join(WORKSPACE_DIR, uuid, fileId) + file_path = os.path.join(WORKSPACE_DIR, uuid, file_id) try: # Ensure the directory exists os.makedirs(os.path.dirname(file_path), exist_ok=True) # Write the content to the file - with open(file_path, "w") as file: + with open(file_path, "w", encoding="utf-8") as file: file.write(content) # Notify the client of the successful update socketio.emit("workspace_file_update_status", {"status": "success"}) - except Exception as e: + except UnexpectedError as e: # Notify the client of an error during the update - socketio.emit( - "workspace_file_update_status", {"status": "error", "message": str(e)} - ) + socketio.emit("workspace_file_update_status", {"status": "error", "message": str(e)}) diff --git a/app/back-end/src/routes/workspace_route.py b/app/back-end/src/routes/workspace_route.py index 89372a5..2765b48 100644 --- a/app/back-end/src/routes/workspace_route.py +++ b/app/back-end/src/routes/workspace_route.py @@ -7,7 +7,8 @@ Dependencies: - os: For file and directory operations, such as checking existence and copying directories. -- shutil: For copying directory trees, ensuring that user-specific directories are properly initialized. +- shutil: For copying directory trees, ensuring that user-specific directories are properly + initialized. - flask.Blueprint: To create and organize route blueprints for modular route management in Flask. - flask.request: To handle incoming HTTP requests and extract headers and parameters. - flask.jsonify: To create JSON responses for API endpoints. @@ -15,16 +16,21 @@ Extensions: - src.setup.extensions.compress: Used for compressing responses to optimize data transfer. -- src.setup.extensions.logger: Provides logging functionalities for capturing and recording events and errors. -- src.setup.constants: Contains constants for directory paths and routes used in the workspace management. +- src.setup.extensions.logger: Provides logging functionalities for capturing and recording events + and errors. +- src.setup.constants: Contains constants for directory paths and routes used in the workspace + management. """ +# pylint: disable=import-error + import os import shutil from flask import Blueprint, request, jsonify, send_file from src.setup.extensions import compress, logger from src.utils.helpers import socketio_emit_to_user_session, build_workspace_structure +from src.utils.exceptions import UnexpectedError from src.constants import ( WORKSPACE_DIR, WORKSPACE_TEMPLATE_DIR, @@ -40,13 +46,14 @@ def get_workspace(): """ Retrieve the structure of the workspace directory. - This endpoint provides a JSON representation of the user's workspace directory structure. If the directory - does not exist, it copies a template directory to the user's workspace. The structure includes metadata - about files and folders in the workspace. + This endpoint provides a JSON representation of the user's workspace directory structure. If the + directory does not exist, it copies a template directory to the user's workspace. The structure + includes metadata about files and folders in the workspace. Process: - Extracts the UUID and SID from request headers to identify the user session. - - Ensures that the user-specific workspace directory exists; if not, copies a template directory. + - Ensures that the user-specific workspace directory exists; if not, copies a template + directory. - Builds the directory structure as a nested JSON object. - Emits feedback to the user's console about the status of the workspace retrieval process. @@ -55,7 +62,8 @@ def get_workspace(): Returns: Response: A Flask response object with the following possible outcomes: - - `200 OK`: If the workspace structure is successfully retrieved, returns a JSON representation of the directory structure. + - `200 OK`: If the workspace structure is successfully retrieved, returns a JSON + representation of the directory structure. - `400 Bad Request`: If the UUID or SID header is missing in the request. - `403 Forbidden`: If there is a permission issue accessing the workspace directory. - `404 Not Found`: If the workspace directory or files are not found. @@ -64,9 +72,11 @@ def get_workspace(): Errors and Feedback: - If the `uuid` or `sid` headers are missing, returns a `400 Bad Request` response. - On successful retrieval, a success message is emitted to the user's console. - - In case of errors, appropriate feedback is emitted to the user's console and an error response is returned: + - In case of errors, appropriate feedback is emitted to the user's console and an error + response is returned: - `FileNotFoundError`: Indicates that the directory or file was not found. - - `PermissionError`: Indicates permission issues while accessing the workspace directory. + - `PermissionError`: Indicates permission issues while accessing the workspace + directory. - Other exceptions: Logs and reports unexpected errors. """ @@ -99,9 +109,7 @@ def get_workspace(): # Build and return the workspace structure as a JSON object workspace_structure = [ - build_workspace_structure( - os.path.join(user_workspace_dir, child), user_workspace_dir - ) + build_workspace_structure(os.path.join(user_workspace_dir, child), user_workspace_dir) for child in os.listdir(user_workspace_dir) ] @@ -117,7 +125,7 @@ def get_workspace(): return jsonify(workspace_structure) except FileNotFoundError as e: - logger.error(f"FileNotFoundError: {e} while accessing {user_workspace_dir}") + logger.error("FileNotFoundError: %s while accessing %s", e, user_workspace_dir) # Emit a feedback to the user's console socketio_emit_to_user_session( CONSOLE_FEEDBACK_EVENT, @@ -130,7 +138,7 @@ def get_workspace(): ) return jsonify({"error": "Requested file not found"}), 404 except PermissionError as e: - logger.error(f"PermissionError: {e} while accessing {user_workspace_dir}") + logger.error("PermissionError: %s while accessing %s", e, user_workspace_dir) # Emit a feedback to the user's console socketio_emit_to_user_session( CONSOLE_FEEDBACK_EVENT, @@ -142,14 +150,14 @@ def get_workspace(): sid, ) return jsonify({"error": "Permission denied"}), 403 - except Exception as e: - logger.error(f"Unexpected error while serving workspace for UUID '{uuid}': {e}") + except UnexpectedError as e: + logger.error("UnexpectedError: %s while accessing %s", e.message, user_workspace_dir) # Emit a feedback to the user's console socketio_emit_to_user_session( CONSOLE_FEEDBACK_EVENT, { "type": "errr", - "message": f"Unexpected error while serving workspace for UUID '{uuid}': {e}", + "message": f"UnexpectedError: {e.message} while accessing {user_workspace_dir}", }, uuid, sid, @@ -165,8 +173,8 @@ def get_workspace_file(relative_path): This endpoint serves files from the user's workspace directory. If the directory does not exist, it copies a template directory to the user's workspace. The file specified by `relative_path` - is then served for download. Feedback about the file retrieval process is sent to the user's console - via Socket.IO. + is then served for download. Feedback about the file retrieval process is sent to the user's + console via Socket.IO. Args: relative_path (str): The path to the file within the user's workspace directory. @@ -182,7 +190,8 @@ def get_workspace_file(relative_path): Errors and Feedback: - If the `uuid` or `sid` headers are missing, a `400 Bad Request` response is returned. - On successful file retrieval, a success message is emitted to the user's console. - - On errors, appropriate feedback is emitted to the user's console and an error response is returned: + - On errors, appropriate feedback is emitted to the user's console and an error response is + returned: - `FileNotFoundError`: Indicates the requested file was not found. - `PermissionError`: Indicates permission issues while accessing the file. - Other exceptions: Logs and reports unexpected errors. @@ -231,7 +240,7 @@ def get_workspace_file(relative_path): return send_file(file_path, as_attachment=False) except FileNotFoundError as e: - logger.error(f"FileNotFoundError: {e} while accessing {file_path}") + logger.error("FileNotFoundError: %s while accessing %s", e, file_path) # Emit a feedback to the user's console socketio_emit_to_user_session( CONSOLE_FEEDBACK_EVENT, @@ -244,7 +253,7 @@ def get_workspace_file(relative_path): ) return jsonify({"error": "Requested file not found"}), 404 except PermissionError as e: - logger.error(f"PermissionError: {e} while accessing {file_path}") + logger.error("PermissionError: %s while accessing %s", e, file_path) # Emit a feedback to the user's console socketio_emit_to_user_session( CONSOLE_FEEDBACK_EVENT, @@ -256,16 +265,14 @@ def get_workspace_file(relative_path): sid, ) return jsonify({"error": "Permission denied"}), 403 - except Exception as e: - logger.error( - f"Unexpected error while serving file '{relative_path}' for UUID '{uuid}': {e}" - ) + except UnexpectedError as e: + logger.error("UnexpectedError: %s while accessing %s", e.message, file_path) # Emit a feedback to the user's console socketio_emit_to_user_session( CONSOLE_FEEDBACK_EVENT, { "type": "errr", - "message": f"Unexpected error while serving file '{relative_path}' for UUID '{uuid}': {e}", + "message": f"UnexpectedError: {e.message} while accessing {file_path}", }, uuid, sid, diff --git a/app/back-end/src/setup/eventer.py b/app/back-end/src/setup/eventer.py index b60206b..ad9f96b 100644 --- a/app/back-end/src/setup/eventer.py +++ b/app/back-end/src/setup/eventer.py @@ -1,30 +1,41 @@ """ This module is responsible for importing and registering all event modules within the application. -Event modules are imported to ensure that their event handlers are registered properly when the application initializes. +Event modules are imported to ensure that their event handlers are registered properly when the +application initializes. -It includes the registration of event handlers for Socket.IO events such as user connections and disconnections. +It includes the registration of event handlers for Socket.IO events such as user connections and +disconnections. Dependencies: -- src.events.workspace_event: An event module related to workspace operations, imported to ensure its event handlers are registered. +- src.events.workspace_event: An event module related to workspace operations, imported to ensure + its event handlers are registered. Functions: - eventer: Registers event handlers for Socket.IO events. Specifically, it handles: - - "connect": Called when a user connects. It registers the user's session and logs the connection. - - "disconnect": Called when a user disconnects. It removes the user's session and logs the disconnection. + - "connect": Called when a user connects. It registers the user's session and logs the + connection. + - "disconnect": Called when a user disconnects. It removes the user's session and logs the + disconnection. Details: -- `handle_connect`: Handles the connection event by extracting the UUID from the request, registering the user session with `socket_manager`, and logging the event. -- `handle_disconnect`: Handles the disconnection event by removing the user session from `socket_manager` and logging the event. +- `handle_connect`: Handles the connection event by extracting the UUID from the request, + registering the user session with `socket_manager`, and logging the event. +- `handle_disconnect`: Handles the disconnection event by removing the user session from + `socket_manager` and logging the event. Logging: -- `logger`: Used to log connection and disconnection events, including error messages when no UUID is provided. +- `logger`: Used to log connection and disconnection events, including error messages when no UUID + is provided. """ +# pylint: disable=import-error + from flask import request from src.setup.extensions import socketio, logger, socket_manager # Import all event modules here +# TODO - Import the workspace_event module to register its event handlers import src.events.workspace_event @@ -38,23 +49,25 @@ def handle_connect(): """ Handle a new Socket.IO connection event. - This function is triggered when a client establishes a connection to the Socket.IO server. It retrieves - the user's UUID from the query parameters, registers the user session using `socket_manager`, and logs - the connection event. + This function is triggered when a client establishes a connection to the Socket.IO server. + It retrieves the user's UUID from the query parameters, registers the user session using + `socket_manager`, and logs the connection event. Process: - Extracts the UUID from the query parameters of the connection request. - Registers the user's session with the `socket_manager` using the UUID and session ID. - Logs the connection event, including the UUID and session ID. - If no UUID is provided in the connection request, an error is logged indicating the missing UUID. + If no UUID is provided in the connection request, an error is logged indicating the missing + UUID. Logs: - Info: When a user successfully connects, including their UUID and session ID. - Error: When the UUID is not provided in the connection request. Returns: - None: This function does not return a value. It performs actions based on the connection event. + None: This function does not return a value. It performs actions based on the connection + event. """ uuid = request.args.get("uuid") if uuid: @@ -68,22 +81,25 @@ def handle_disconnect(): """ Handle a Socket.IO disconnection event. - This function is triggered when a client disconnects from the Socket.IO server. It retrieves the user's - UUID from the query parameters, removes the user session using `socket_manager`, and logs the disconnection event. + This function is triggered when a client disconnects from the Socket.IO server. It retrieves + the user's UUID from the query parameters, removes the user session using `socket_manager`, + and logs the disconnection event. Process: - Extracts the UUID from the query parameters of the disconnection request. - Removes the user's session from `socket_manager` using the UUID and session ID. - Logs the disconnection event, including the UUID and session ID. - If no UUID is provided in the disconnection request, an error is logged indicating the missing UUID. + If no UUID is provided in the disconnection request, an error is logged indicating the + missing UUID. Logs: - Info: When a user successfully disconnects, including their UUID and session ID. - Error: When the UUID is not provided in the disconnection request. Returns: - None: This function does not return a value. It performs actions based on the disconnection event. + None: This function does not return a value. It performs actions based on the + disconnection event. """ uuid = request.args.get("uuid") if uuid: diff --git a/app/back-end/src/setup/extensions.py b/app/back-end/src/setup/extensions.py index d550a13..c58d4df 100644 --- a/app/back-end/src/setup/extensions.py +++ b/app/back-end/src/setup/extensions.py @@ -2,25 +2,34 @@ This module sets up and configures logging and initializes key Flask extensions for the application. The module is responsible for: -- Configuring the logging system to provide consistent log formatting and log levels for monitoring and debugging. +- Configuring the logging system to provide consistent log formatting and log levels for monitoring + and debugging. - Initializing essential Flask extensions including: - - `Compress`: For handling response compression to improve performance and reduce bandwidth usage. - - `SocketIO`: For real-time communication using WebSocket support. - - `CORS`: For managing Cross-Origin Resource Sharing (CORS) policies to control access between different domains. + - `Compress`: For handling response compression to improve performance and reduce bandwidth + usage. + - `SocketIO`: For real-time communication using WebSocket support. + - `CORS`: For managing Cross-Origin Resource Sharing (CORS) policies to control access between + different domains. Dependencies: -- logging: Python's standard module for logging messages, used to configure logging behavior and format. +- logging: Python's standard module for logging messages, used to configure logging behavior and + format. - flask_compress: Flask extension that provides response compression capabilities. - flask_socketio: Flask extension that adds WebSocket support and real-time communication. -- flask_cors: Flask extension that handles CORS (Cross-Origin Resource Sharing) to manage cross-domain requests. +- flask_cors: Flask extension that handles CORS (Cross-Origin Resource Sharing) to manage + cross-domain requests. Initialization: - **Logging**: Configured to output log messages with a specific format and log level. - **Env**: An instance of `Env` from `src.config` is used to load environment variables. -- **SocketManager**: Initialized with the Redis URL from environment variables for managing WebSocket sessions. -- **Flask Extensions**: Instances of `Compress`, `SocketIO`, and `CORS` are created and ready to be integrated into the Flask application. +- **SocketManager**: Initialized with the Redis URL from environment variables for managing + WebSocket sessions. +- **Flask Extensions**: Instances of `Compress`, `SocketIO`, and `CORS` are created and ready + to be integrated into the Flask application. """ +# pylint: disable=import-error + import logging from flask_compress import Compress from flask_socketio import SocketIO diff --git a/app/back-end/src/setup/router.py b/app/back-end/src/setup/router.py index c188c98..fa998ee 100644 --- a/app/back-end/src/setup/router.py +++ b/app/back-end/src/setup/router.py @@ -2,7 +2,8 @@ This module defines the router setup for the Flask application. It is responsible for: -- Creating a main blueprint (`router_bp`) that serves as the central point for registering other route blueprints. +- Creating a main blueprint (`router_bp`) that serves as the central point for registering + other route blueprints. - Registering all the individual route blueprints with a specified URL prefix. Dependencies: @@ -10,6 +11,8 @@ - src.routes.workspace_route: Contains the blueprint for workspace-related routes. """ +# pylint: disable=import-error + from flask import Blueprint from src.routes.workspace_route import workspace_route_bp diff --git a/app/back-end/src/utils/exceptions.py b/app/back-end/src/utils/exceptions.py new file mode 100644 index 0000000..402f70d --- /dev/null +++ b/app/back-end/src/utils/exceptions.py @@ -0,0 +1,41 @@ +""" +This module defines custom exception classes for the application. + +It provides: +- `UnexpectedError`: A custom exception class used to signal unexpected errors + that occur during the execution of the application. + +Dependencies: +- Exception: The base class for all built-in exceptions in Python. +""" + + +class UnexpectedError(Exception): + """ + Exception raised for unexpected errors in the application. + + This exception is used to indicate that an error has occurred which was not + anticipated or handled explicitly. It allows for custom error messages to + be provided when raising the exception. + + Args: + message (str): A descriptive message about the error. + + Attributes: + message (str): The error message provided during the exception initialization. + + Inherits: + Exception: The base class for all built-in exceptions in Python. + """ + + def __init__(self, message): + """ + Initialize an instance of the `UnexpectedError` exception. + + Args: + message (str): A descriptive message about the error. This message + is stored in the `message` attribute and is passed + to the base `Exception` class. + """ + self.message = message + super().__init__(self.message) diff --git a/app/back-end/src/utils/helpers.py b/app/back-end/src/utils/helpers.py index 6eb418f..0aecce0 100644 --- a/app/back-end/src/utils/helpers.py +++ b/app/back-end/src/utils/helpers.py @@ -1,32 +1,37 @@ """ -This package provides utilities for handling Socket.IO events and managing workspace directory structures. +This package provides utilities for handling Socket.IO events and managing workspace directory +structures. Functions: - socketio_emit_to_user_session: Sends a Socket.IO event to a specific user session. The event data is augmented with a timestamp indicating the current time. - build_workspace_structure: Recursively builds a dictionary representation of a directory structure - for a given workspace. It includes metadata about files and directories and provides a hierarchical - view of the workspace. + for a given workspace. It includes metadata about files and directories and provides a + hierarchical view of the workspace. Dependencies: - os: Provides a way to interact with the operating system, including filesystem operations. - datetime: Supplies classes for manipulating dates and times. -- src.setup.extensions: Contains `socketio` and `socket_manager` used for emitting events and managing - user sessions in Socket.IO. +- src.setup.extensions: Contains `socketio` and `socket_manager` used for emitting events and + managing user sessions in Socket.IO. Details: -- `socketio_emit_to_user_session` emits an event to a specific user session identified by UUID and session ID (SID). -- `build_workspace_structure` generates a nested dictionary structure representing the directories and - files within a workspace, providing metadata such as labels and types. +- `socketio_emit_to_user_session` emits an event to a specific user session identified by UUID + and session ID (SID). +- `build_workspace_structure` generates a nested dictionary structure representing the directories + and files within a workspace, providing metadata such as labels and types. Usage: - Use `socketio_emit_to_user_session` to communicate with specific user sessions through Socket.IO. -- Use `build_workspace_structure` to construct a detailed, recursive view of a directory structure for - applications requiring hierarchical data representation. +- Use `build_workspace_structure` to construct a detailed, recursive view of a directory structure + for applications requiring hierarchical data representation. -No classes or modules are directly exposed by this package, only the utility functions defined above. +No classes or modules are directly exposed by this package, only the utility functions defined +above. """ +# pylint: disable=import-error + import os from datetime import datetime @@ -82,8 +87,10 @@ def build_workspace_structure(path, user_workspace_dir): dict: A dictionary representing the directory structure. Each entry contains: - "id" (str): # The relative path of the item from the `user_workspace_dir`. - "label" (str): # The name of the file or directory. - - "fileType" (str): # The type of the item, either "folder" for directories or "csv" for files. - - "children" (list): # A list of child items, which is empty for files and populated with nested dictionaries for directories. + - "fileType" (str): # The type of the item, either "folder" for directories or "csv" + for files. + - "children" (list): # A list of child items, which is empty for files and populated + with nested dictionaries for directories. """ workspace_structure = { "id": os.path.relpath(path, user_workspace_dir), diff --git a/app/back-end/src/utils/socket_manager.py b/app/back-end/src/utils/socket_manager.py index 2c94a10..82f5e32 100644 --- a/app/back-end/src/utils/socket_manager.py +++ b/app/back-end/src/utils/socket_manager.py @@ -1,32 +1,161 @@ +""" +This module provides the `SocketManager` class for managing WebSocket user sessions using Redis. + +The `SocketManager` class is responsible for: +- Registering user sessions by associating a user UUID with a socket ID. +- Removing user sessions when users disconnect. +- Retrieving active sessions for a specific user or all users. + +Dependencies: +- redis: Python Redis client for interacting with the Redis store. + +Usage: + socket_manager = SocketManager(redis_url="redis://localhost:6379/0") + socket_manager.register_user_session(uuid, sid) + sessions = socket_manager.get_user_sessions(uuid) +""" + +# pylint: disable=import-error + import redis class SocketManager: - def __init__(self, redis_url="redis://localhost:6379/0", namespace="socket_id_map"): + """ + Manages WebSocket user sessions using Redis as a backend. + + This class provides methods to register, remove, and retrieve user sessions + associated with WebSocket connections. It uses Redis to store session data, + enabling efficient management and retrieval of session information. + + Attributes: + redis (StrictRedis): A Redis client for interacting with the Redis store. + namespace (str): A namespace used as a prefix for Redis keys to avoid key collisions. + + Methods: + register_user_session(uuid, sid): Registers a socket ID (sid) for a given user UUID. + remove_user_session(uuid, sid): Removes a socket ID (sid) from a given user UUID. + get_user_sessions(uuid): Retrieves all socket IDs associated with a given user UUID. + get_user_session(uuid, sid): Checks if a specific socket ID (sid) exists for a given user + UUID. + get_all_sessions(): Retrieves all active sessions for all users. + remove_all_sessions(): Removes all stored sessions in the namespace. + """ + + def __init__( + self, + redis_url="redis://localhost:6379/0", + namespace="socket_id_map", + ): + """ + Initializes the SocketManager with a connection to a Redis instance and a specified + namespace. + + Args: + redis_url (str): The URL of the Redis instance to connect to. Defaults + to "redis://localhost:6379/0". + namespace (str): The namespace used as a prefix for Redis keys to prevent key + collisions. Defaults to "socket_id_map". + + Attributes: + redis (StrictRedis): A Redis client instance for performing operations in the + Redis store. + namespace (str): The namespace that will be used to prefix all Redis keys associated + with user sessions. + """ self.redis = redis.StrictRedis.from_url(redis_url) self.namespace = namespace def _get_redis_key(self): + """ + Constructs the base Redis key using the defined namespace. + + Returns: + str: The base Redis key, which is the namespace used for all user session keys. + """ return f"{self.namespace}" def register_user_session(self, uuid, sid): + """ + Registers a user's session ID (sid) in Redis under their unique identifier (uuid). + + This method adds the session ID to a Redis set associated with the user's UUID, + allowing for multiple active sessions per user. + + Args: + uuid (str): The unique identifier for the user. + sid (str): The session ID to be associated with the user's UUID. + + Returns: + None + """ self.redis.sadd(f"{self._get_redis_key()}:{uuid}", sid) def remove_user_session(self, uuid, sid): + """ + Removes a user's session ID (sid) from Redis. + + This method removes the specified session ID from the Redis set associated with + the user's UUID. If the set becomes empty after removal, the set is deleted. + + Args: + uuid (str): The unique identifier for the user. + sid (str): The session ID to be removed from the user's session set. + + Returns: + None + """ self.redis.srem(f"{self._get_redis_key()}:{uuid}", sid) if self.redis.scard(f"{self._get_redis_key()}:{uuid}") == 0: self.redis.delete(f"{self._get_redis_key()}:{uuid}") def get_user_sessions(self, uuid): + """ + Retrieves a list of session IDs for a given user UUID. + + This method fetches all session IDs associated with the specified user UUID + from Redis and decodes them from bytes to strings. + + Args: + uuid (str): The unique identifier for the user. + + Returns: + list: A list of session IDs (strings) associated with the given UUID. + """ sids = self.redis.smembers(f"{self._get_redis_key()}:{uuid}") return [sid.decode("utf-8") for sid in sids] def get_user_session(self, uuid, sid): + """ + Checks if a specific session ID exists for a given user UUID. + + This method retrieves all session IDs associated with the specified user UUID + from Redis and checks if the provided session ID is among them. It returns + the session ID if it exists, otherwise it returns None. + + Args: + uuid (str): The unique identifier for the user. + sid (str): The session ID to check. + + Returns: + str or None: The session ID if it exists for the given UUID, otherwise None. + """ sids = self.redis.smembers(f"{self._get_redis_key()}:{uuid}") return sid if sid in [s.decode("utf-8") for s in sids] else None def get_all_sessions(self): + """ + Retrieves all user sessions from Redis. + + This method fetches all keys matching the pattern for user sessions and + constructs a dictionary where each key is a user UUID and the value is + a list of session IDs associated with that UUID. + + Returns: + dict: A dictionary mapping user UUIDs to lists of session IDs. Each key + is a UUID, and the value is a list of session IDs (strings) for that UUID. + """ keys = self.redis.keys(f"{self._get_redis_key()}:*") return { key.decode("utf-8").split(":")[1]: [ @@ -36,6 +165,17 @@ def get_all_sessions(self): } def remove_all_sessions(self): + """ + Removes all user sessions from Redis. + + This method deletes all session keys from Redis that match the pattern + for user sessions. This operation clears out all stored session information + for all users. + + Returns: + None: This method does not return any value. It performs the action of + deleting the session data. + """ keys = self.redis.keys(f"{self._get_redis_key()}:*") for key in keys: self.redis.delete(key) diff --git a/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx b/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx index 16adf5b..332636d 100644 --- a/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx +++ b/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx @@ -1,7 +1,7 @@ import { useWorkspaceContext } from '@/features/editor/hooks'; import { getIconFromFileType, isExpandable } from '@/features/editor/utils'; import { FileTypes } from '@/types'; -import FolderRounded from '@mui/icons-material/FolderRounded'; +import { FolderRounded as FolderRoundedIcon } from '@mui/icons-material'; import Collapse from '@mui/material/Collapse'; import { alpha, styled } from '@mui/material/styles'; import { TransitionProps } from '@mui/material/transitions'; @@ -128,7 +128,7 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem( const expandable = isExpandable(children); let icon; if (expandable) { - icon = FolderRounded; + icon = FolderRoundedIcon; } else if (item.fileType) { icon = getIconFromFileType(item.fileType); }