From 08c546f40aae12c0fee5333fca163b6052658c49 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 29 Oct 2025 13:58:08 -0400 Subject: [PATCH] Fix WSGI server initialization by extracting data source setup Addresses the issue where Gunicorn/uWSGI servers import the gateway module but never call main(), leaving item_sources empty and causing the file crawler to fail. Changes: - Extract data source initialization into initialize_data_sources() - Call initialization at module import time for WSGI compatibility - Add _initialized flag to prevent double initialization - Simplify main() to delegate to initialize_data_sources() This ensures data sources are populated when running under WSGI servers (Gunicorn, uWSGI) while maintaining backward compatibility with the Flask development server. Related to GitHub issues #33 and #92 --- cellxgene_gateway/gateway.py | 62 ++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/cellxgene_gateway/gateway.py b/cellxgene_gateway/gateway.py index 1b9c14e..1e9017b 100644 --- a/cellxgene_gateway/gateway.py +++ b/cellxgene_gateway/gateway.py @@ -78,6 +78,42 @@ if ( cache = BackendCache() +# Initialize data sources - this is defined later in the file but called here +# to ensure initialization happens when WSGI servers (Gunicorn) import the module +def initialize_data_sources(): + """Initialize data sources from environment variables. + Called at module import time for WSGI server compatibility (Gunicorn). + Uses a guard flag to prevent double initialization within a process.""" + global default_item_source + + logging.basicConfig( + level=env.log_level, + format="%(asctime)s:%(name)s:%(levelname)s:%(message)s", + ) + logger = logging.getLogger(__name__) + + cellxgene_data = os.environ.get("CELLXGENE_DATA", None) + cellxgene_bucket = os.environ.get("CELLXGENE_BUCKET", None) + + if cellxgene_bucket is not None: + from cellxgene_gateway.items.s3.s3item_source import S3ItemSource + + item_sources.append(S3ItemSource(cellxgene_bucket, name="s3")) + default_item_source = "s3" + logger.info("Initialized S3 data source") + logger.debug(f"S3 bucket: {cellxgene_bucket}") + if cellxgene_data is not None: + from cellxgene_gateway.items.file.fileitem_source import FileItemSource + + item_sources.append(FileItemSource(cellxgene_data, name="local")) + default_item_source = "local" + logger.info("Initialized local file data source") + logger.debug(f"Data directory: {cellxgene_data}") + if len(item_sources) == 0: + raise Exception("Please specify CELLXGENE_DATA or CELLXGENE_BUCKET") + flask_util.include_source_in_url = len(item_sources) > 1 + + @app.errorhandler(CellxgeneException) def handle_invalid_usage(error): message = f"{error.http_status} Error : {error.message}" @@ -294,29 +330,13 @@ def launch(): app.launchtime = current_time_stamp() app.run(host="0.0.0.0", port=env.gateway_port, debug=False) +# When using servers like Gunicorn or uWSGI, this file is imported rather than run directly. +# As a result, the main() function is never called automatically. +# Therefore, we must initialize the data sources at import time to ensure they are available. +initialize_data_sources() def main(): - logging.basicConfig( - level=env.log_level, - format="%(asctime)s:%(name)s:%(levelname)s:%(message)s", - ) - cellxgene_data = os.environ.get("CELLXGENE_DATA", None) - cellxgene_bucket = os.environ.get("CELLXGENE_BUCKET", None) - - if cellxgene_bucket is not None: - from cellxgene_gateway.items.s3.s3item_source import S3ItemSource - - item_sources.append(S3ItemSource(cellxgene_bucket, name="s3")) - default_item_source = "s3" - if cellxgene_data is not None: - from cellxgene_gateway.items.file.fileitem_source import FileItemSource - - item_sources.append(FileItemSource(cellxgene_data, name="local")) - default_item_source = "local" - if len(item_sources) == 0: - raise Exception("Please specify CELLXGENE_DATA or CELLXGENE_BUCKET") - flask_util.include_source_in_url = len(item_sources) > 1 - + """CLI entry point for Flask development server.""" launch()