Repository: inventree/InvenTree

Issue:
[FR] Add typecasting to certain settings
Typecasting in the settings for:

Looks like there are 6 envs that need to be int (that I can find)
- [x] INVENTREE_LOGIN_CONFIRM_DAYS
- [x] INVENTREE_LOGIN_ATTEMPTS
- [x] INVENTREE_EMAIL_PORT
- [x] INVENTREE_DB_PORT
- [x] INVENTREE_CACHE_PORT
~~INVENTREE_WEB_PORT~~

_Originally posted by @stavros-k in https://github.com/inventree/InvenTree/issues/3655#issuecomment-1259866326_
      


Discussion:
`INVENTREE_WEB_PORT` is a setting for gunicorn - not shure how to do that and if that is even necesarry.
> `INVENTREE_WEB_PORT` is a setting for gunicorn - not shure how to do that and if that is even necesarry.

It seems to accept strings fine, (as i use a different internal port and works)

It's a bit of an odd issues, because environment variables are inherently strings. There is no int environment variable, that simply does not exist on Linux.

What I think is happening, is that some dependencies or code portions, are more forgiving with this than others. Which explains why the type is only triggering issues on some environment variables.

Typecasting should fix this on the source of origin, which is totally fine :)

Resolving patch:
diff --git a/InvenTree/InvenTree/config.py b/InvenTree/InvenTree/config.py
--- a/InvenTree/InvenTree/config.py
+++ b/InvenTree/InvenTree/config.py
@@ -58,7 +58,7 @@ def load_config_data() -> map:
     return data
 
 
-def get_setting(env_var=None, config_key=None, default_value=None):
+def get_setting(env_var=None, config_key=None, default_value=None, typecast=None):
     """Helper function for retrieving a configuration setting value.
 
     - First preference is to look for the environment variable
@@ -69,15 +69,24 @@ def get_setting(env_var=None, config_key=None, default_value=None):
         env_var: Name of the environment variable e.g. 'INVENTREE_STATIC_ROOT'
         config_key: Key to lookup in the configuration file
         default_value: Value to return if first two options are not provided
-
+        typecast: Function to use for typecasting the value
     """
+    def try_typecasting(value):
+        """Attempt to typecast the value"""
+        if typecast is not None:
+            # Try to typecast the value
+            try:
+                return typecast(value)
+            except Exception as error:
+                logger.error(f"Failed to typecast '{env_var}' with value '{value}' to type '{typecast}' with error {error}")
+        return value
 
     # First, try to load from the environment variables
     if env_var is not None:
         val = os.getenv(env_var, None)
 
         if val is not None:
-            return val
+            return try_typecasting(val)
 
     # Next, try to load from configuration file
     if config_key is not None:
@@ -96,10 +105,10 @@ def get_setting(env_var=None, config_key=None, default_value=None):
             cfg_data = cfg_data[key]
 
         if result is not None:
-            return result
+            return try_typecasting(result)
 
     # Finally, return the default value
-    return default_value
+    return try_typecasting(default_value)
 
 
 def get_boolean_setting(env_var=None, config_key=None, default_value=False):
diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py
--- a/InvenTree/InvenTree/settings.py
+++ b/InvenTree/InvenTree/settings.py
@@ -348,6 +348,12 @@
     env_var = os.environ.get(env_key, None)
 
     if env_var:
+        # Make use PORT is int
+        if key == 'PORT':
+            try:
+                env_var = int(env_var)
+            except ValueError:
+                logger.error(f"Invalid number for {env_key}: {env_var}")
         # Override configuration value
         db_config[key] = env_var
 
@@ -505,7 +511,7 @@
 
 # Cache configuration
 cache_host = get_setting('INVENTREE_CACHE_HOST', 'cache.host', None)
-cache_port = get_setting('INVENTREE_CACHE_PORT', 'cache.port', '6379')
+cache_port = get_setting('INVENTREE_CACHE_PORT', 'cache.port', '6379', typecast=int)
 
 if cache_host:  # pragma: no cover
     # We are going to rely upon a possibly non-localhost for our cache,
@@ -671,7 +677,7 @@
 # Email configuration options
 EMAIL_BACKEND = get_setting('INVENTREE_EMAIL_BACKEND', 'email.backend', 'django.core.mail.backends.smtp.EmailBackend')
 EMAIL_HOST = get_setting('INVENTREE_EMAIL_HOST', 'email.host', '')
-EMAIL_PORT = int(get_setting('INVENTREE_EMAIL_PORT', 'email.port', 25))
+EMAIL_PORT = get_setting('INVENTREE_EMAIL_PORT', 'email.port', 25, typecast=int)
 EMAIL_HOST_USER = get_setting('INVENTREE_EMAIL_USERNAME', 'email.username', '')
 EMAIL_HOST_PASSWORD = get_setting('INVENTREE_EMAIL_PASSWORD', 'email.password', '')
 EMAIL_SUBJECT_PREFIX = get_setting('INVENTREE_EMAIL_PREFIX', 'email.prefix', '[InvenTree] ')
@@ -720,8 +726,8 @@
 SOCIALACCOUNT_STORE_TOKENS = True
 
 # settings for allauth
-ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', 'login_confirm_days', 3)
-ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', 'login_attempts', 5)
+ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', 'login_confirm_days', 3, typecast=int)
+ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', 'login_attempts', 5, typecast=int)
 ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
 ACCOUNT_PREVENT_ENUMERATION = True
 


Test patch: