Repository: langflow-ai/langflow
Pull request: feat: improve CLI parameter handling and cleanup unused code
This PR refines CLI parameter handling, adds default path values, enforces parameter hierarchy, and removes unused code for improved maintainability.

Fixes #3820

Problem statement:
langflow not respecting env file.
### Bug Description

I start langflow like this:
python3 -m langflow  run  --env-file .langflow.env
it doesn't respect the env file.

Here is the env file I am using:
LANGFLOW_SUPERUSER=admin
LANGFLOW_SUPERUSER_PASSWORD=password
LANGFLOW_AUTO_LOGIN=false
LANGFLOW_HOST=0.0.0.0





### Reproduction

1. pip install langlow

### Expected behavior

it should respect the .env file.

### Who can help?

_No response_

### Operating System

Ubuntu 20.04.4 LTS

### Langflow Version

1.0.18

### Python Version

3.10

### Screenshot

_No response_

### Flow File

_No response_

Hints from issue discussion:
<!-- Answer -->
There are a couple of similar issues that might be relevant to your problem:

1. [Features change when langflow is run from the Env Variables](https://github.com/langflow-ai/langflow/issues/2969) - This issue was closed and the solution involved ensuring that the correct options were included in the configuration files.
2. [LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT not loading any environment variables](https://github.com/langflow-ai/langflow/issues/3021) - This issue was also closed, and the solution was to format the environment variables as a comma-separated list instead of a JSON-encoded string.

For your specific issue, ensure that the `--env-file` parameter is correctly passed in the command. The correct command should be:

```sh
python3 -m langflow run --env-file .langflow.env
```

Make sure that the `.env` file is correctly named and located in the same directory from which you are running the command. According to the Langflow documentation, the `--env-file` option specifies the path to the `.env` file containing environment variables, and the default is `.env`. The code snippet shows that the `env_file` parameter is used to load environment variables if provided:

```python
if env_file:
    load_dotenv(env_file, override=True)
```

Additionally, other configuration files or settings in Langflow, such as `config.yaml` and command-line options, might override or conflict with the `.env` file settings <sup>[[1]](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/__main__.py)</sup><sup>[[2]](https://github.com/langflow-ai/langflow/blob/dev/README.md)</sup><sup>[[3]](https://github.com/logspace-ai/langflow/blob/dev/src/backend/langflow/__main__.py)</sup>.


<!-- Dosu Comment Footer -->
*To continue talking to [Dosu](https://dosu.dev), mention @dosu.*

Hi there! How’s it going?
I followed the steps you described, and everything worked perfectly for me. I had to log in using 'admin' and 'password'. I also tried using a different password, and it worked as expected.
Could you be more specific about the error or behavior you're encountering?
I think I understand your issue.
If you try the following command, I believe you won't encounter any problems:

```
export $(xargs < .langflow.env) && langflow run --env-file .langflow.env
```

This happens because we have two configuration methods: one through the CLI using `typer`, and another via a configuration file using `pydantic-settings`.

The `--env-file` parameter in `typer`, which specifies an alternate file with environment variables, can't override its own settings like `--host`. Therefore, for it to work as expected, you would need to have already declared `LANGFLOW_HOST` in your environment.

It's definitely confusing and should be improved.

Commit message: feat: update function to enforce parameter hierarchy

CLI > specific env_file > default env_file

Patch to review:
diff --git a/Makefile b/Makefile
index 6fa3b6293908..c28b7123f671 100644
--- a/Makefile
+++ b/Makefile
@@ -243,7 +243,7 @@ start:
 
 ifeq ($(open_browser),false)
 	@make install_backend && uv run langflow run \
-		--path $(path) \
+		--frontend-path $(path) \
 		--log-level $(log_level) \
 		--host $(host) \
 		--port $(port) \
@@ -251,7 +251,7 @@ ifeq ($(open_browser),false)
 		--no-open-browser
 else
 	@make install_backend && uv run langflow run \
-		--path $(path) \
+		--frontend-path $(path) \
 		--log-level $(log_level) \
 		--host $(host) \
 		--port $(port) \
@@ -262,7 +262,7 @@ setup_devcontainer: ## set up the development container
 	make install_backend
 	make install_frontend
 	make build_frontend
-	uv run langflow --path src/frontend/build
+	uv run langflow --frontend-path src/frontend/build
 
 setup_env: ## set up the environment
 	@sh ./scripts/setup/setup_env.sh
diff --git a/src/backend/base/langflow/__main__.py b/src/backend/base/langflow/__main__.py
index 3a77fd82bbc1..97408f81050a 100644
--- a/src/backend/base/langflow/__main__.py
+++ b/src/backend/base/langflow/__main__.py
@@ -1,3 +1,4 @@
+import inspect
 import platform
 import socket
 import sys
@@ -28,7 +29,6 @@
 from langflow.services.deps import get_db_service, get_settings_service, session_scope
 from langflow.services.settings.constants import DEFAULT_SUPERUSER
 from langflow.services.utils import initialize_services
-from langflow.utils.util import update_settings
 from langflow.utils.version import fetch_latest_version, get_version_info
 from langflow.utils.version import is_pre_release as langflow_is_pre_release
 
@@ -79,69 +79,73 @@ def set_var_for_macos_issue():
 
 @app.command()
 def run(
-    host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
-    workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
-    timeout: int = typer.Option(300, help="Worker timeout in seconds.", envvar="LANGFLOW_WORKER_TIMEOUT"),
-    port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
+    host: str | None = typer.Option(None, help="Host to bind the server to.", show_default=False),
+    workers: int | None = typer.Option(None, help="Number of worker processes.", show_default=False),
+    worker_timeout: int | None = typer.Option(None, help="Worker timeout in seconds.", show_default=False),
+    port: int | None = typer.Option(None, help="Port to listen on.", show_default=False),
     components_path: Path | None = typer.Option(
-        Path(__file__).parent / "components",
+        None,
         help="Path to the directory containing custom components.",
-        envvar="LANGFLOW_COMPONENTS_PATH",
+        show_default=False,
     ),
     # .env file param
-    env_file: Path = typer.Option(None, help="Path to the .env file containing environment variables."),
-    log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
-    log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"),
+    env_file: Path | None = typer.Option(
+        None,
+        help="Path to the .env file containing environment variables.",
+        show_default=False,
+    ),
+    log_level: str | None = typer.Option(None, help="Logging level.", show_default=False),
+    log_file: Path | None = typer.Option(None, help="Path to the log file.", show_default=False),
     cache: str | None = typer.Option(
-        envvar="LANGFLOW_LANGCHAIN_CACHE",
+        None,
         help="Type of cache to use. (InMemoryCache, SQLiteCache)",
-        default=None,
+        show_default=False,
     ),
-    dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
-    path: str = typer.Option(
+    dev: bool | None = typer.Option(None, help="Run in development mode (may contain bugs)", show_default=False),
+    frontend_path: str | None = typer.Option(
         None,
         help="Path to the frontend directory containing build files. This is for development purposes only.",
-        envvar="LANGFLOW_FRONTEND_PATH",
+        show_default=False,
     ),
-    open_browser: bool = typer.Option(
-        True,
+    open_browser: bool | None = typer.Option(
+        None,
         help="Open the browser after starting the server.",
-        envvar="LANGFLOW_OPEN_BROWSER",
+        show_default=False,
     ),
-    remove_api_keys: bool = typer.Option(
-        False,
+    remove_api_keys: bool | None = typer.Option(
+        None,
         help="Remove API keys from the projects saved in the database.",
-        envvar="LANGFLOW_REMOVE_API_KEYS",
+        show_default=False,
     ),
-    backend_only: bool = typer.Option(
-        False,
+    backend_only: bool | None = typer.Option(
+        None,
         help="Run only the backend server without the frontend.",
-        envvar="LANGFLOW_BACKEND_ONLY",
+        show_default=False,
     ),
-    store: bool = typer.Option(
-        True,
+    store: bool | None = typer.Option(
+        None,
         help="Enables the store features.",
-        envvar="LANGFLOW_STORE",
+        show_default=False,
     ),
-    auto_saving: bool = typer.Option(
-        True,
+    auto_saving: bool | None = typer.Option(
+        None,
         help="Defines if the auto save is enabled.",
-        envvar="LANGFLOW_AUTO_SAVING",
+        show_default=False,
     ),
-    auto_saving_interval: int = typer.Option(
-        1000,
+    auto_saving_interval: int | None = typer.Option(
+        None,
         help="Defines the debounce time for the auto save.",
-        envvar="LANGFLOW_AUTO_SAVING_INTERVAL",
+        show_default=False,
     ),
-    health_check_max_retries: bool = typer.Option(
-        True,
+    health_check_max_retries: bool | None = typer.Option(
+        None,
         help="Defines the number of retries for the health check.",
-        envvar="LANGFLOW_HEALTH_CHECK_MAX_RETRIES",
+        show_default=False,
     ),
-    max_file_size_upload: int = typer.Option(
-        100,
+    max_file_size_upload: int | None = typer.Option(
+        None,
         help="Defines the maximum file size for the upload in MB.",
-        envvar="LANGFLOW_MAX_FILE_SIZE_UPLOAD",
+        show_default=False,
     ),
 ):
     """
@@ -153,33 +157,36 @@ def run(
 
     if env_file:
         load_dotenv(env_file, override=True)
+        logger.debug(f"Loading config from file: '{env_file}'")
 
-    update_settings(
-        dev=dev,
-        remove_api_keys=remove_api_keys,
-        cache=cache,
-        components_path=components_path,
-        store=store,
-        auto_saving=auto_saving,
-        auto_saving_interval=auto_saving_interval,
-        health_check_max_retries=health_check_max_retries,
-        max_file_size_upload=max_file_size_upload,
-    )
-    # create path object if path is provided
-    static_files_dir: Path | None = Path(path) if path else None
     settings_service = get_settings_service()
-    settings_service.set("backend_only", backend_only)
+
+    arguments, _, _, values = inspect.getargvalues(inspect.currentframe())
+    valid_args = [arg for arg in arguments if values[arg] is not None]
+    for arg in valid_args:
+        if hasattr(settings_service.settings, arg):
+            settings_service.set(arg, values[arg])
+            logger.debug(f"Loading config from cli parameter '{arg}': '{values[arg]}'")
+
+    host = settings_service.settings.host
+    port = settings_service.settings.port
+    workers = settings_service.settings.workers
+    log_level = settings_service.settings.log_level
+    frontend_path = settings_service.settings.frontend_path
+    backend_only = settings_service.settings.backend_only
+
+    # create path object if frontend_path is provided
+    static_files_dir: Path | None = Path(frontend_path) if frontend_path else None
+
     app = setup_app(static_files_dir=static_files_dir, backend_only=backend_only)
     # check if port is being used
     if is_port_in_use(port, host):
         port = get_free_port(port)
 
-    settings_service.set("worker_timeout", timeout)
-
     options = {
         "bind": f"{host}:{port}",
         "workers": get_number_of_workers(workers),
-        "timeout": timeout,
+        "timeout": worker_timeout,
     }
 
     # Define an env variable to know if we are just testing the server
@@ -472,7 +479,7 @@ def migration(
 
 @app.command()
 def api_key(
-    log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
+    log_level: str = typer.Option("error", help="Logging level."),
 ):
     """
     Creates an API key for the default superuser if AUTO_LOGIN is enabled.
diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py
index 8aa9b8ff4ad4..a33c58460c59 100644
--- a/src/backend/base/langflow/services/settings/base.py
+++ b/src/backend/base/langflow/services/settings/base.py
@@ -94,7 +94,7 @@ class Settings(BaseSettings):
     """The port on which Langflow will expose Prometheus metrics. 9090 is the default port."""
 
     remove_api_keys: bool = False
-    components_path: list[str] = []
+    components_path: list[str] = [str(Path(__file__).parent / "components")]
     langchain_cache: str = "InMemoryCache"
     load_flows_path: str | None = None
 
@@ -146,6 +146,20 @@ class Settings(BaseSettings):
     """If set to True, Langflow will keep track of each vertex builds (outputs) in the UI for any flow."""
 
     # Config
+    host: str = "127.0.0.1"
+    """The host on which Langflow will run."""
+    port: int = 7860
+    """The port on which Langflow will run."""
+    workers: int = 1
+    """The number of workers to run."""
+    log_level: str = "critical"
+    """The log level for Langflow."""
+    log_file: str | None = "logs/langflow.log"
+    """The path to log file for Langflow."""
+    frontend_path: str | None = None
+    """The path to the frontend directory containing build files. This is for development purposes only.."""
+    open_browser: bool = False
+    """If set to True, Langflow will open the browser on startup."""
     auto_saving: bool = True
     """If set to True, Langflow will auto save flows."""
     auto_saving_interval: int = 1000


Code review comments:
diff_hunk: @@ -79,69 +79,73 @@ def set_var_for_macos_issue():
 
 @app.command()
 def run(
-    host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
-    workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
-    timeout: int = typer.Option(300, help="Worker timeout in seconds.", envvar="LANGFLOW_WORKER_TIMEOUT"),
-    port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
+    host: str | None = typer.Option(None, help="Host to bind the server to.", show_default=False),
+    workers: int | None = typer.Option(None, help="Number of worker processes.", show_default=False),
+    worker_timeout: int | None = typer.Option(None, help="Worker timeout in seconds.", show_default=False),
+    port: int | None = typer.Option(None, help="Port to listen on.", show_default=False),
     components_path: Path | None = typer.Option(
-        Path(__file__).parent / "components",
+        str(Path(__file__).parent / "components"),
path: src/backend/base/langflow/__main__.py
text: @user1:
Should `components_path ` type be changed to `str` ?