From e232e2ab7f9d4953cad711994c9975f7cd5488b2 Mon Sep 17 00:00:00 2001
From: Gregoo <versatile.mailbox@gmail.com>
Date: Mon, 20 Jan 2025 15:20:07 +0100
Subject: [PATCH] Don't store complex objects in Flash config that could mask
 existing config items, rather store the values and handle the actual list of
 conf differently

---
 app.py                                  | 12 +++----
 bricktracker/app.py                     |  4 +--
 bricktracker/configuration.py           |  1 +
 bricktracker/configuration_list.py      | 44 ++++++++++++++++---------
 bricktracker/instructions.py            |  8 ++---
 bricktracker/instructions_list.py       |  5 ++-
 bricktracker/login.py                   |  6 ++--
 bricktracker/minifigure.py              |  8 ++---
 bricktracker/minifigure_list.py         |  4 +--
 bricktracker/part.py                    | 12 +++----
 bricktracker/part_list.py               | 12 ++-----
 bricktracker/rebrickable.py             |  4 +--
 bricktracker/rebrickable_image.py       | 16 ++++-----
 bricktracker/rebrickable_minifigures.py |  2 +-
 bricktracker/rebrickable_parts.py       |  4 +--
 bricktracker/rebrickable_set.py         |  4 +--
 bricktracker/retired_list.py            | 10 +++---
 bricktracker/set.py                     |  6 ++--
 bricktracker/set_list.py                |  4 +--
 bricktracker/socket.py                  |  8 ++---
 bricktracker/sql.py                     |  6 ++--
 bricktracker/theme_list.py              | 10 +++---
 bricktracker/views/add.py               |  8 ++---
 bricktracker/views/admin.py             |  6 ++--
 bricktracker/views/instructions.py      |  2 +-
 bricktracker/wish_list.py               |  2 +-
 templates/add.html                      |  2 +-
 templates/admin.html                    |  2 +-
 templates/admin/database.html           |  4 +--
 templates/admin/instructions.html       |  4 +--
 templates/admin/retired.html            |  2 +-
 templates/admin/theme.html              |  2 +-
 templates/base.html                     |  4 +--
 templates/index.html                    |  8 ++---
 templates/instructions/upload.html      |  4 +--
 templates/macro/accordion.html          |  2 +-
 templates/macro/table.html              |  2 +-
 37 files changed, 126 insertions(+), 118 deletions(-)

diff --git a/app.py b/app.py
index 2077083f..8cea2efb 100644
--- a/app.py
+++ b/app.py
@@ -20,19 +20,19 @@ setup_app(app)
 # Create the socket
 s = BrickSocket(
     app,
-    threaded=not app.config['NO_THREADED_SOCKET'].value,
+    threaded=not app.config['NO_THREADED_SOCKET'],
 )
 
 
 if __name__ == '__main__':
     # Run the application
     logger.info('Starting BrickTracker on {host}:{port}'.format(
-        host=app.config['HOST'].value,
-        port=app.config['PORT'].value,
+        host=app.config['HOST'],
+        port=app.config['PORT'],
     ))
     s.socket.run(
         app,
-        host=app.config['HOST'].value,
-        debug=app.config['DEBUG'].value,
-        port=app.config['PORT'].value,
+        host=app.config['HOST'],
+        debug=app.config['DEBUG'],
+        port=app.config['PORT'],
     )
diff --git a/bricktracker/app.py b/bricktracker/app.py
index 5ba4414d..a1bfb277 100644
--- a/bricktracker/app.py
+++ b/bricktracker/app.py
@@ -28,7 +28,7 @@ def setup_app(app: Flask) -> None:
     BrickConfigurationList(app)
 
     # Set the logging level
-    if app.config['DEBUG'].value:
+    if app.config['DEBUG']:
         logging.basicConfig(
             stream=sys.stdout,
             level=logging.DEBUG,
@@ -90,7 +90,7 @@ def setup_app(app: Flask) -> None:
         g.request_time = request_time
 
         # Register the timezone
-        g.timezone = ZoneInfo(current_app.config['TIMEZONE'].value)
+        g.timezone = ZoneInfo(current_app.config['TIMEZONE'])
 
         # Version
         g.version = __version__
diff --git a/bricktracker/configuration.py b/bricktracker/configuration.py
index b9c3559c..d5c561b7 100644
--- a/bricktracker/configuration.py
+++ b/bricktracker/configuration.py
@@ -69,6 +69,7 @@ class BrickConfiguration(object):
                 # Remove static prefix
                 value = value.removeprefix('static/')
 
+        # Type casting
         if self.cast is not None:
             self.value = self.cast(value)
         else:
diff --git a/bricktracker/configuration_list.py b/bricktracker/configuration_list.py
index 0e93a88d..3293d85c 100644
--- a/bricktracker/configuration_list.py
+++ b/bricktracker/configuration_list.py
@@ -1,46 +1,60 @@
+import logging
 from typing import Generator
 
-from flask import current_app, Flask
+from flask import Flask
 
 from .config import CONFIG
 from .configuration import BrickConfiguration
 from .exceptions import ConfigurationMissingException
 
+logger = logging.getLogger(__name__)
+
 
 # Application configuration
 class BrickConfigurationList(object):
     app: Flask
+    configurations: dict[str, BrickConfiguration]
 
     # Load configuration
     def __init__(self, app: Flask, /):
         self.app = app
 
-        # Process all configuration items
-        for config in CONFIG:
-            item = BrickConfiguration(**config)
-            self.app.config[item.name] = item
+        # Load the configurations only there is none already loaded
+        configurations = getattr(self, 'configurations', None)
+
+        if configurations is None:
+            logger.info('Loading configuration variables')
+
+            BrickConfigurationList.configurations = {}
+
+            # Process all configuration items
+            for config in CONFIG:
+                item = BrickConfiguration(**config)
+
+                # Store in the list
+                BrickConfigurationList.configurations[item.name] = item
+
+                # Only store the value in the app to avoid breaking any
+                # existing variables
+                self.app.config[item.name] = item.value
 
     # Check whether a str configuration is set
     @staticmethod
     def error_unless_is_set(name: str):
-        config: BrickConfiguration = current_app.config[name]
+        configuration = BrickConfigurationList.configurations[name]
 
-        if config.value is None or config.value == '':
+        if configuration.value is None or configuration.value == '':
             raise ConfigurationMissingException(
                 '{name} must be defined (using the {environ} environment variable)'.format(  # noqa: E501
-                    name=config.name,
-                    environ=config.env_name
+                    name=name,
+                    environ=configuration.env_name
                 ),
             )
 
     # Get all the configuration items from the app config
     @staticmethod
     def list() -> Generator[BrickConfiguration, None, None]:
-        keys = list(current_app.config.keys())
-        keys.sort()
+        keys = sorted(BrickConfigurationList.configurations.keys())
 
         for name in keys:
-            config = current_app.config[name]
-
-            if isinstance(config, BrickConfiguration):
-                yield config
+            yield BrickConfigurationList.configurations[name]
diff --git a/bricktracker/instructions.py b/bricktracker/instructions.py
index 6aaa0505..622fdc38 100644
--- a/bricktracker/instructions.py
+++ b/bricktracker/instructions.py
@@ -39,7 +39,7 @@ class BrickInstructions(object):
         # Store the name and extension, check if extension is allowed
         self.name, self.extension = os.path.splitext(self.filename)
         self.extension = self.extension.lower()
-        self.allowed = self.extension in current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value  # noqa: E501
+        self.allowed = self.extension in current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS']  # noqa: E501
 
         # Placeholder
         self.brickset = None
@@ -67,7 +67,7 @@ class BrickInstructions(object):
     # Display the time in a human format
     def human_time(self) -> str:
         return self.mtime.astimezone(g.timezone).strftime(
-            current_app.config['FILE_DATETIME_FORMAT'].value
+            current_app.config['FILE_DATETIME_FORMAT']
         )
 
     # Compute the path of an instruction file
@@ -77,7 +77,7 @@ class BrickInstructions(object):
 
         return os.path.join(
             current_app.static_folder,  # type: ignore
-            current_app.config['INSTRUCTIONS_FOLDER'].value,
+            current_app.config['INSTRUCTIONS_FOLDER'],
             filename
         )
 
@@ -118,7 +118,7 @@ class BrickInstructions(object):
         if not self.allowed:
             return ''
 
-        folder: str = current_app.config['INSTRUCTIONS_FOLDER'].value
+        folder: str = current_app.config['INSTRUCTIONS_FOLDER']
 
         # Compute the path
         path = os.path.join(folder, self.filename)
diff --git a/bricktracker/instructions_list.py b/bricktracker/instructions_list.py
index 57329fec..4d447b9d 100644
--- a/bricktracker/instructions_list.py
+++ b/bricktracker/instructions_list.py
@@ -36,7 +36,7 @@ class BrickInstructionsList(object):
                 # Make a folder relative to static
                 folder: str = os.path.join(
                     current_app.static_folder,  # type: ignore
-                    current_app.config['INSTRUCTIONS_FOLDER'].value,
+                    current_app.config['INSTRUCTIONS_FOLDER'],
                 )
 
                 for file in os.scandir(folder):
@@ -68,9 +68,8 @@ class BrickInstructionsList(object):
                 for brickset in BrickSetList().generic().records:
                     bricksets[brickset.fields.set_num] = brickset
 
-                # Return the files
+                # Inject the brickset if it exists
                 for instruction in self.all.values():
-                    # Inject the brickset if it exists
                     if (
                         instruction.allowed and
                         instruction.number is not None and
diff --git a/bricktracker/login.py b/bricktracker/login.py
index 87e6683d..2642464d 100644
--- a/bricktracker/login.py
+++ b/bricktracker/login.py
@@ -12,7 +12,7 @@ class LoginManager(object):
 
     def __init__(self, app: Flask, /):
         # Setup basic authentication
-        app.secret_key = app.config['AUTHENTICATION_KEY'].value
+        app.secret_key = app.config['AUTHENTICATION_KEY']
 
         manager = login_manager.LoginManager()
         manager.login_view = 'login.login'  # type: ignore
@@ -23,11 +23,11 @@ class LoginManager(object):
         def user_loader(*arg) -> LoginManager.User:
             return self.User(
                 'admin',
-                app.config['AUTHENTICATION_PASSWORD'].value
+                app.config['AUTHENTICATION_PASSWORD']
             )
 
         # If the password is unset, globally disable
-        app.config['LOGIN_DISABLED'] = app.config['AUTHENTICATION_PASSWORD'].value == ''  # noqa: E501
+        app.config['LOGIN_DISABLED'] = app.config['AUTHENTICATION_PASSWORD'] == ''  # noqa: E501
 
     # Tells whether the user is authenticated, meaning:
     # - Authentication disabled
diff --git a/bricktracker/minifigure.py b/bricktracker/minifigure.py
index 7a7adf61..afe54638 100644
--- a/bricktracker/minifigure.py
+++ b/bricktracker/minifigure.py
@@ -119,7 +119,7 @@ class BrickMinifigure(BrickRecord):
 
     # Compute the url for minifigure part image
     def url_for_image(self, /) -> str:
-        if not current_app.config['USE_REMOTE_IMAGES'].value:
+        if not current_app.config['USE_REMOTE_IMAGES']:
             if self.fields.set_img_url is None:
                 file = RebrickableImage.nil_minifigure_name()
             else:
@@ -128,15 +128,15 @@ class BrickMinifigure(BrickRecord):
             return RebrickableImage.static_url(file, 'MINIFIGURES_FOLDER')
         else:
             if self.fields.set_img_url is None:
-                return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'].value  # noqa: E501
+                return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE']
             else:
                 return self.fields.set_img_url
 
     # Compute the url for the rebrickable page
     def url_for_rebrickable(self, /) -> str:
-        if current_app.config['REBRICKABLE_LINKS'].value:
+        if current_app.config['REBRICKABLE_LINKS']:
             try:
-                return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].value.format(  # noqa: E501
+                return current_app.config['REBRICKABLE_LINK_MINIFIGURE_PATTERN'].format(  # noqa: E501
                     number=self.fields.fig_num.lower(),
                 )
             except Exception:
diff --git a/bricktracker/minifigure_list.py b/bricktracker/minifigure_list.py
index dd00c2df..971fc6f0 100644
--- a/bricktracker/minifigure_list.py
+++ b/bricktracker/minifigure_list.py
@@ -27,7 +27,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
         self.brickset = None
 
         # Store the order for this list
-        self.order = current_app.config['MINIFIGURES_DEFAULT_ORDER'].value
+        self.order = current_app.config['MINIFIGURES_DEFAULT_ORDER']
 
     # Load all minifigures
     def all(self, /) -> Self:
@@ -44,7 +44,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
     # Last added minifigure
     def last(self, /, limit: int = 6) -> Self:
         # Randomize
-        if current_app.config['RANDOM'].value:
+        if current_app.config['RANDOM']:
             order = 'RANDOM()'
         else:
             order = 'minifigures.rowid DESC'
diff --git a/bricktracker/part.py b/bricktracker/part.py
index e14b6c0d..dd71bbba 100644
--- a/bricktracker/part.py
+++ b/bricktracker/part.py
@@ -190,9 +190,9 @@ class BrickPart(BrickRecord):
 
     # Compute the url for the bricklink page
     def url_for_bricklink(self, /) -> str:
-        if current_app.config['BRICKLINK_LINKS'].value:
+        if current_app.config['BRICKLINK_LINKS']:
             try:
-                return current_app.config['BRICKLINK_LINK_PART_PATTERN'].value.format(  # noqa: E501
+                return current_app.config['BRICKLINK_LINK_PART_PATTERN'].format(  # noqa: E501
                     number=self.fields.part_num,
                 )
             except Exception:
@@ -202,7 +202,7 @@ class BrickPart(BrickRecord):
 
     # Compute the url for the part image
     def url_for_image(self, /) -> str:
-        if not current_app.config['USE_REMOTE_IMAGES'].value:
+        if not current_app.config['USE_REMOTE_IMAGES']:
             if self.fields.part_img_url is None:
                 file = RebrickableImage.nil_name()
             else:
@@ -211,7 +211,7 @@ class BrickPart(BrickRecord):
             return RebrickableImage.static_url(file, 'PARTS_FOLDER')
         else:
             if self.fields.part_img_url is None:
-                return current_app.config['REBRICKABLE_IMAGE_NIL'].value
+                return current_app.config['REBRICKABLE_IMAGE_NIL']
             else:
                 return self.fields.part_img_url
 
@@ -234,9 +234,9 @@ class BrickPart(BrickRecord):
 
     # Compute the url for the rebrickable page
     def url_for_rebrickable(self, /) -> str:
-        if current_app.config['REBRICKABLE_LINKS'].value:
+        if current_app.config['REBRICKABLE_LINKS']:
             try:
-                return current_app.config['REBRICKABLE_LINK_PART_PATTERN'].value.format(  # noqa: E501
+                return current_app.config['REBRICKABLE_LINK_PART_PATTERN'].format(  # noqa: E501
                     number=self.fields.part_num,
                     color=self.fields.color_id,
                 )
diff --git a/bricktracker/part_list.py b/bricktracker/part_list.py
index d6e69453..3932d8e0 100644
--- a/bricktracker/part_list.py
+++ b/bricktracker/part_list.py
@@ -30,7 +30,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
         self.minifigure = None
 
         # Store the order for this list
-        self.order = current_app.config['PARTS_DEFAULT_ORDER'].value
+        self.order = current_app.config['PARTS_DEFAULT_ORDER']
 
     # Load all parts
     def all(self, /) -> Self:
@@ -63,10 +63,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
                 record=record,
             )
 
-            if (
-                current_app.config['SKIP_SPARE_PARTS'].value and
-                part.fields.is_spare
-            ):
+            if current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare:
                 continue
 
             self.records.append(part)
@@ -92,10 +89,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
                 record=record,
             )
 
-            if (
-                current_app.config['SKIP_SPARE_PARTS'].value and
-                part.fields.is_spare
-            ):
+            if current_app.config['SKIP_SPARE_PARTS'] and part.fields.is_spare:
                 continue
 
             self.records.append(part)
diff --git a/bricktracker/rebrickable.py b/bricktracker/rebrickable.py
index 3f34fdd1..312797a9 100644
--- a/bricktracker/rebrickable.py
+++ b/bricktracker/rebrickable.py
@@ -77,7 +77,7 @@ class Rebrickable(Generic[T]):
 
         # Bootstrap a first set of parameters
         parameters: dict[str, Any] | None = {
-            'page_size': current_app.config['REBRICKABLE_PAGE_SIZE'].value,
+            'page_size': current_app.config['REBRICKABLE_PAGE_SIZE'],
         }
 
         # Read all pages
@@ -115,7 +115,7 @@ class Rebrickable(Generic[T]):
     # Load from the API
     def load(self, /, parameters: dict[str, Any] = {}) -> dict[str, Any]:
         # Inject the API key
-        parameters['api_key'] = current_app.config['REBRICKABLE_API_KEY'].value,  # noqa: E501
+        parameters['api_key'] = current_app.config['REBRICKABLE_API_KEY']
 
         try:
             return json.loads(
diff --git a/bricktracker/rebrickable_image.py b/bricktracker/rebrickable_image.py
index 513e4b2f..d0a07ec6 100644
--- a/bricktracker/rebrickable_image.py
+++ b/bricktracker/rebrickable_image.py
@@ -70,12 +70,12 @@ class RebrickableImage(object):
     # Return the folder depending on the objects provided
     def folder(self, /) -> str:
         if self.part is not None:
-            return current_app.config['PARTS_FOLDER'].value
+            return current_app.config['PARTS_FOLDER']
 
         if self.minifigure is not None:
-            return current_app.config['MINIFIGURES_FOLDER'].value
+            return current_app.config['MINIFIGURES_FOLDER']
 
-        return current_app.config['SETS_FOLDER'].value
+        return current_app.config['SETS_FOLDER']
 
     # Return the id depending on the objects provided
     def id(self, /) -> str:
@@ -105,13 +105,13 @@ class RebrickableImage(object):
     def url(self, /) -> str:
         if self.part is not None:
             if self.part.fields.part_img_url is None:
-                return current_app.config['REBRICKABLE_IMAGE_NIL'].value
+                return current_app.config['REBRICKABLE_IMAGE_NIL']
             else:
                 return self.part.fields.part_img_url
 
         if self.minifigure is not None:
             if self.minifigure.fields.set_img_url is None:
-                return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'].value  # noqa: E501
+                return current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE']
             else:
                 return self.minifigure.fields.set_img_url
 
@@ -122,7 +122,7 @@ class RebrickableImage(object):
     def nil_name() -> str:
         filename, _ = os.path.splitext(
             os.path.basename(
-                urlparse(current_app.config['REBRICKABLE_IMAGE_NIL'].value).path  # noqa: E501
+                urlparse(current_app.config['REBRICKABLE_IMAGE_NIL']).path
             )
         )
 
@@ -133,7 +133,7 @@ class RebrickableImage(object):
     def nil_minifigure_name() -> str:
         filename, _ = os.path.splitext(
             os.path.basename(
-                urlparse(current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE'].value).path  # noqa: E501
+                urlparse(current_app.config['REBRICKABLE_IMAGE_NIL_MINIFIGURE']).path  # noqa: E501
             )
         )
 
@@ -142,7 +142,7 @@ class RebrickableImage(object):
     # Return the static URL for an image given a name and folder
     @staticmethod
     def static_url(name: str, folder_name: str) -> str:
-        folder: str = current_app.config[folder_name].value
+        folder: str = current_app.config[folder_name]
 
         # /!\ Everything is saved as .jpg, even if it came from a .png
         # not changing this behaviour.
diff --git a/bricktracker/rebrickable_minifigures.py b/bricktracker/rebrickable_minifigures.py
index b34284ab..bcf165bd 100644
--- a/bricktracker/rebrickable_minifigures.py
+++ b/bricktracker/rebrickable_minifigures.py
@@ -71,7 +71,7 @@ class RebrickableMinifigures(object):
                 )
             )
 
-            if not current_app.config['USE_REMOTE_IMAGES'].value:
+            if not current_app.config['USE_REMOTE_IMAGES']:
                 RebrickableImage(
                     self.brickset,
                     minifigure=minifigure
diff --git a/bricktracker/rebrickable_parts.py b/bricktracker/rebrickable_parts.py
index 1049b926..6ef9abb9 100644
--- a/bricktracker/rebrickable_parts.py
+++ b/bricktracker/rebrickable_parts.py
@@ -76,7 +76,7 @@ class RebrickableParts(object):
         for index, part in enumerate(inventory):
             # Skip spare parts
             if (
-                current_app.config['SKIP_SPARE_PARTS'].value and
+                current_app.config['SKIP_SPARE_PARTS'] and
                 part.fields.is_spare
             ):
                 continue
@@ -104,7 +104,7 @@ class RebrickableParts(object):
                 )
             )
 
-            if not current_app.config['USE_REMOTE_IMAGES'].value:
+            if not current_app.config['USE_REMOTE_IMAGES']:
                 RebrickableImage(
                     self.brickset,
                     minifigure=self.minifigure,
diff --git a/bricktracker/rebrickable_set.py b/bricktracker/rebrickable_set.py
index b6ccf169..329c4118 100644
--- a/bricktracker/rebrickable_set.py
+++ b/bricktracker/rebrickable_set.py
@@ -55,7 +55,7 @@ class RebrickableSet(object):
             # Insert into database
             brickset.insert(commit=False)
 
-            if not current_app.config['USE_REMOTE_IMAGES'].value:
+            if not current_app.config['USE_REMOTE_IMAGES']:
                 RebrickableImage(brickset).download()
 
             # Load the inventory
@@ -210,5 +210,5 @@ class RebrickableSet(object):
         # Insert into database
         brickwish.insert()
 
-        if not current_app.config['USE_REMOTE_IMAGES'].value:
+        if not current_app.config['USE_REMOTE_IMAGES']:
             RebrickableImage(brickwish).download()
diff --git a/bricktracker/retired_list.py b/bricktracker/retired_list.py
index e76a6990..78c3a68f 100644
--- a/bricktracker/retired_list.py
+++ b/bricktracker/retired_list.py
@@ -33,7 +33,7 @@ class BrickRetiredList(object):
 
             # Try to read the themes from a CSV file
             try:
-                with open(current_app.config['RETIRED_SETS_PATH'].value, newline='') as themes_file:  # noqa: E501
+                with open(current_app.config['RETIRED_SETS_PATH'], newline='') as themes_file:  # noqa: E501
                     themes_reader = csv.reader(themes_file)
 
                     # Ignore the header
@@ -44,7 +44,7 @@ class BrickRetiredList(object):
                         BrickRetiredList.retired[retired.number] = retired
 
                 # File stats
-                stat = os.stat(current_app.config['RETIRED_SETS_PATH'].value)
+                stat = os.stat(current_app.config['RETIRED_SETS_PATH'])
                 BrickRetiredList.size = stat.st_size
                 BrickRetiredList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)  # noqa: E501
 
@@ -79,7 +79,7 @@ class BrickRetiredList(object):
     def human_time(self) -> str:
         if self.mtime is not None:
             return self.mtime.astimezone(g.timezone).strftime(
-                current_app.config['FILE_DATETIME_FORMAT'].value
+                current_app.config['FILE_DATETIME_FORMAT']
             )
         else:
             return ''
@@ -88,7 +88,7 @@ class BrickRetiredList(object):
     @staticmethod
     def update() -> None:
         response = requests.get(
-            current_app.config['RETIRED_SETS_FILE_URL'].value,
+            current_app.config['RETIRED_SETS_FILE_URL'],
             stream=True,
         )
 
@@ -99,7 +99,7 @@ class BrickRetiredList(object):
 
         content = gzip.GzipFile(fileobj=response.raw)
 
-        with open(current_app.config['RETIRED_SETS_PATH'].value, 'wb') as f:
+        with open(current_app.config['RETIRED_SETS_PATH'], 'wb') as f:
             copyfileobj(content, f)
 
     logger.info('Retired sets list updated')
diff --git a/bricktracker/set.py b/bricktracker/set.py
index 2f1778e1..f2cb9f83 100644
--- a/bricktracker/set.py
+++ b/bricktracker/set.py
@@ -157,7 +157,7 @@ class BrickSet(BrickRecord):
 
     # Compute the url for the set image
     def url_for_image(self, /) -> str:
-        if not current_app.config['USE_REMOTE_IMAGES'].value:
+        if not current_app.config['USE_REMOTE_IMAGES']:
             return RebrickableImage.static_url(
                 self.fields.set_num,
                 'SETS_FOLDER'
@@ -182,9 +182,9 @@ class BrickSet(BrickRecord):
 
     # Compute the url for the rebrickable page
     def url_for_rebrickable(self, /) -> str:
-        if current_app.config['REBRICKABLE_LINKS'].value:
+        if current_app.config['REBRICKABLE_LINKS']:
             try:
-                return current_app.config['REBRICKABLE_LINK_SET_PATTERN'].value.format(  # noqa: E501
+                return current_app.config['REBRICKABLE_LINK_SET_PATTERN'].format(  # noqa: E501
                     number=self.fields.set_num.lower(),
                 )
             except Exception:
diff --git a/bricktracker/set_list.py b/bricktracker/set_list.py
index 6bbd1bf4..41d94a6f 100644
--- a/bricktracker/set_list.py
+++ b/bricktracker/set_list.py
@@ -26,7 +26,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
         self.themes = []
 
         # Store the order for this list
-        self.order = current_app.config['SETS_DEFAULT_ORDER'].value
+        self.order = current_app.config['SETS_DEFAULT_ORDER']
 
     # All the sets
     def all(self, /) -> Self:
@@ -60,7 +60,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
     # Last added sets
     def last(self, /, limit: int = 6) -> Self:
         # Randomize
-        if current_app.config['RANDOM'].value:
+        if current_app.config['RANDOM']:
             order = 'RANDOM()'
         else:
             order = 'sets.rowid DESC'
diff --git a/bricktracker/socket.py b/bricktracker/socket.py
index 44ddadfb..8b7a25fd 100644
--- a/bricktracker/socket.py
+++ b/bricktracker/socket.py
@@ -56,19 +56,19 @@ class BrickSocket(object):
 
         # Compute the namespace
         self.namespace = '/{namespace}'.format(
-            namespace=app.config['SOCKET_NAMESPACE'].value
+            namespace=app.config['SOCKET_NAMESPACE']
         )
 
         # Inject CORS if a domain is defined
-        if app.config['DOMAIN_NAME'].value != '':
-            kwargs['cors_allowed_origins'] = app.config['DOMAIN_NAME'].value
+        if app.config['DOMAIN_NAME'] != '':
+            kwargs['cors_allowed_origins'] = app.config['DOMAIN_NAME']
 
         # Instantiate the socket
         self.socket = SocketIO(
             self.app,
             *args,
             **kwargs,
-            path=app.config['SOCKET_PATH'].value,
+            path=app.config['SOCKET_PATH'],
             async_mode='eventlet',
         )
 
diff --git a/bricktracker/sql.py b/bricktracker/sql.py
index 77aa7b53..f6f478d5 100644
--- a/bricktracker/sql.py
+++ b/bricktracker/sql.py
@@ -37,7 +37,7 @@ class BrickSQL(object):
 
             logger.debug('SQLite3: connect')
             self.connection = sqlite3.connect(
-                current_app.config['DATABASE_PATH'].value
+                current_app.config['DATABASE_PATH']
             )
 
             # Setup the row factory to get pseudo-dicts rather than tuples
@@ -249,7 +249,7 @@ class BrickSQL(object):
     # Delete the database
     @staticmethod
     def delete() -> None:
-        os.remove(current_app.config['DATABASE_PATH'].value)
+        os.remove(current_app.config['DATABASE_PATH'])
 
         # Info
         logger.info('The database has been deleted')
@@ -292,7 +292,7 @@ class BrickSQL(object):
     # Replace the database with a new file
     @staticmethod
     def upload(file: FileStorage, /) -> None:
-        file.save(current_app.config['DATABASE_PATH'].value)
+        file.save(current_app.config['DATABASE_PATH'])
 
         # Info
         logger.info('The database has been imported using file {file}'.format(
diff --git a/bricktracker/theme_list.py b/bricktracker/theme_list.py
index d8a0b475..160530ce 100644
--- a/bricktracker/theme_list.py
+++ b/bricktracker/theme_list.py
@@ -33,7 +33,7 @@ class BrickThemeList(object):
 
             # Try to read the themes from a CSV file
             try:
-                with open(current_app.config['THEMES_PATH'].value, newline='') as themes_file:  # noqa: E501
+                with open(current_app.config['THEMES_PATH'], newline='') as themes_file:  # noqa: E501
                     themes_reader = csv.reader(themes_file)
 
                     # Ignore the header
@@ -44,7 +44,7 @@ class BrickThemeList(object):
                         BrickThemeList.themes[theme.id] = theme
 
                 # File stats
-                stat = os.stat(current_app.config['THEMES_PATH'].value)
+                stat = os.stat(current_app.config['THEMES_PATH'])
                 BrickThemeList.size = stat.st_size
                 BrickThemeList.mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)  # noqa: E501
 
@@ -78,7 +78,7 @@ class BrickThemeList(object):
     def human_time(self) -> str:
         if self.mtime is not None:
             return self.mtime.astimezone(g.timezone).strftime(
-                current_app.config['FILE_DATETIME_FORMAT'].value
+                current_app.config['FILE_DATETIME_FORMAT']
             )
         else:
             return ''
@@ -87,7 +87,7 @@ class BrickThemeList(object):
     @staticmethod
     def update() -> None:
         response = requests.get(
-            current_app.config['THEMES_FILE_URL'].value,
+            current_app.config['THEMES_FILE_URL'],
             stream=True,
         )
 
@@ -98,7 +98,7 @@ class BrickThemeList(object):
 
         content = gzip.GzipFile(fileobj=response.raw)
 
-        with open(current_app.config['THEMES_PATH'].value, 'wb') as f:
+        with open(current_app.config['THEMES_PATH'], 'wb') as f:
             copyfileobj(content, f)
 
         logger.info('Theme list updated')
diff --git a/bricktracker/views/add.py b/bricktracker/views/add.py
index 45b08a38..218a0bfa 100644
--- a/bricktracker/views/add.py
+++ b/bricktracker/views/add.py
@@ -17,8 +17,8 @@ def add() -> str:
 
     return render_template(
         'add.html',
-        path=current_app.config['SOCKET_PATH'].value,
-        namespace=current_app.config['SOCKET_NAMESPACE'].value,
+        path=current_app.config['SOCKET_PATH'],
+        namespace=current_app.config['SOCKET_NAMESPACE'],
         messages=MESSAGES
     )
 
@@ -32,7 +32,7 @@ def bulk() -> str:
 
     return render_template(
         'bulk.html',
-        path=current_app.config['SOCKET_PATH'].value,
-        namespace=current_app.config['SOCKET_NAMESPACE'].value,
+        path=current_app.config['SOCKET_PATH'],
+        namespace=current_app.config['SOCKET_NAMESPACE'],
         messages=MESSAGES
     )
diff --git a/bricktracker/views/admin.py b/bricktracker/views/admin.py
index 77b8c634..5dcb1579 100644
--- a/bricktracker/views/admin.py
+++ b/bricktracker/views/admin.py
@@ -160,19 +160,19 @@ def do_delete_database() -> Response:
 def download_database() -> Response:
     # Create a file name with a timestamp embedded
     name, extension = os.path.splitext(
-        os.path.basename(current_app.config['DATABASE_PATH'].value)
+        os.path.basename(current_app.config['DATABASE_PATH'])
     )
 
     # Info
     logger.info('The database has been downloaded')
 
     return send_file(
-        current_app.config['DATABASE_PATH'].value,
+        current_app.config['DATABASE_PATH'],
         as_attachment=True,
         download_name='{name}-{timestamp}{extension}'.format(
             name=name,
             timestamp=datetime.now().astimezone(g.timezone).strftime(
-                current_app.config['DATABASE_TIMESTAMP_FORMAT'].value
+                current_app.config['DATABASE_TIMESTAMP_FORMAT']
             ),
             extension=extension
         )
diff --git a/bricktracker/views/instructions.py b/bricktracker/views/instructions.py
index 6145914a..047e961c 100644
--- a/bricktracker/views/instructions.py
+++ b/bricktracker/views/instructions.py
@@ -114,7 +114,7 @@ def do_upload() -> Response:
     file = upload_helper(
         'file',
         'instructions.upload',
-        extensions=current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value,
+        extensions=current_app.config['INSTRUCTIONS_ALLOWED_EXTENSIONS'],
     )
 
     if isinstance(file, Response):
diff --git a/bricktracker/wish_list.py b/bricktracker/wish_list.py
index 266ee43d..8c55408d 100644
--- a/bricktracker/wish_list.py
+++ b/bricktracker/wish_list.py
@@ -18,7 +18,7 @@ class BrickWishList(BrickRecordList[BrickWish]):
     def all(self, /) -> Self:
         # Load the wished sets from the database
         for record in self.select(
-            order=current_app.config['WISHES_DEFAULT_ORDER'].value
+            order=current_app.config['WISHES_DEFAULT_ORDER']
         ):
             brickwish = BrickWish(record=record)
 
diff --git a/templates/add.html b/templates/add.html
index a4a49179..1251525c 100644
--- a/templates/add.html
+++ b/templates/add.html
@@ -4,7 +4,7 @@
 
 {% block main %}
 <div class="container">
-  {% if not config['HIDE_ADD_BULK_SET'].value %}
+  {% if not config['HIDE_ADD_BULK_SET'] %}
   <div class="alert alert-primary" role="alert">
     <h4 class="alert-heading">Too many to add?</h4>
     <p class="mb-0">You can import multiple sets at once with <a href="{{ url_for('add.bulk') }}" class="btn btn-primary"><i class="ri-function-add-line"></i> Bulk add</a>.</p>
diff --git a/templates/admin.html b/templates/admin.html
index 9e172bca..e425887d 100644
--- a/templates/admin.html
+++ b/templates/admin.html
@@ -21,7 +21,7 @@
             {% else %}
               {% include 'admin/logout.html' %}
               {% include 'admin/instructions.html' %}
-              {% if not config['USE_REMOTE_IMAGES'].value %}
+              {% if not config['USE_REMOTE_IMAGES'] %}
                 {% include 'admin/image.html' %}
               {% endif %}
               {% include 'admin/theme.html' %}
diff --git a/templates/admin/database.html b/templates/admin/database.html
index bbe02d93..70f767c2 100644
--- a/templates/admin/database.html
+++ b/templates/admin/database.html
@@ -6,7 +6,7 @@
 {% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
 {% if not is_init %}
   <div class="alert alert-warning" role="alert">
-    <p>The database file is: <code>{{ config['DATABASE_PATH'].value }}</code>. The database is not initialized.</p>
+    <p>The database file is: <code>{{ config['DATABASE_PATH'] }}</code>. The database is not initialized.</p>
     <hr>
     <form action="{{ url_for('admin.init_database') }}" method="post" class="text-end">
         <button type="submit" class="btn btn-warning"><i class="ri-reset-right-fill"></i> Initialize the database</button>
@@ -25,7 +25,7 @@
       </form>
     </div>
   {% endif %}
-  <p>The database file is: <code>{{ config['DATABASE_PATH'].value }}</code>. <i class="ri-checkbox-circle-line"></i> The database is initialized.</p>
+  <p>The database file is: <code>{{ config['DATABASE_PATH'] }}</code>. <i class="ri-checkbox-circle-line"></i> The database is initialized.</p>
   <p>
     <a href="{{ url_for('admin.download_database') }}" class="btn btn-primary" role="button"><i class="ri-download-line"></i> Download the database file</a>
   </p>
diff --git a/templates/admin/instructions.html b/templates/admin/instructions.html
index 17c1e4f0..04298cf8 100644
--- a/templates/admin/instructions.html
+++ b/templates/admin/instructions.html
@@ -3,8 +3,8 @@
 {{ accordion.header('Instructions', 'instructions', 'admin', expanded=open_instructions, icon='file-line') }}
 <h5 class="border-bottom">Folder</h5>
 <p>
-  The instructions files folder is: <code>{{ config['INSTRUCTIONS_FOLDER'].value }}</code>. <br>
-  Allowed file formats for instructions are the following: <code>{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</code>.
+  The instructions files folder is: <code>{{ config['INSTRUCTIONS_FOLDER'] }}</code>. <br>
+  Allowed file formats for instructions are the following: <code>{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS']) }}</code>.
 </p>
 <h5 class="border-bottom">Counters</h5>
 <p>
diff --git a/templates/admin/retired.html b/templates/admin/retired.html
index 121e47f7..4d8762ac 100644
--- a/templates/admin/retired.html
+++ b/templates/admin/retired.html
@@ -4,7 +4,7 @@
 <h5 class="border-bottom">File</h5>
 {% if retired.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the retired sets: {{ retired.exception }}</div>{% endif %}
 <p>
-  The retired sets file is: <code>{{ config['RETIRED_SETS_PATH'].value }}</code>.
+  The retired sets file is: <code>{{ config['RETIRED_SETS_PATH'] }}</code>.
   {% if retired.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ retired.human_size() }}</span>{% endif %}
   {% if retired.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ retired.human_time() }}</span>{% endif %}
 </p>
diff --git a/templates/admin/theme.html b/templates/admin/theme.html
index c935e6f5..f64e5b76 100644
--- a/templates/admin/theme.html
+++ b/templates/admin/theme.html
@@ -4,7 +4,7 @@
 <h5 class="border-bottom">File</h5>
 {% if theme.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the themes: {{ theme.exception }}</div>{% endif %}
 <p>
-  The themes file is: <code>{{ config['THEMES_PATH'].value }}</code>.
+  The themes file is: <code>{{ config['THEMES_PATH'] }}</code>.
   {% if theme.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ theme.human_size() }}</span>{% endif %}
   {% if theme.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ theme.human_time() }}</span>{% endif %}
 </p>
diff --git a/templates/base.html b/templates/base.html
index 2985f46d..23badab3 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -25,7 +25,7 @@
         <div class="collapse navbar-collapse" id="navbarSupportedContent">
           <ul class="navbar-nav me-auto mb-2 mb-lg-0">
             {% for item in config['_NAVBAR'] %}
-              {% if item.flag and not config[item.flag].value %}
+              {% if item.flag and not config[item.flag] %}
                 <li class="nav-item px-1">
                   <a {% if request.url_rule.endpoint == item.endpoint %}class="nav-link active" aria-current="page"{% else %}class="nav-link"{% endif %} href="{{ url_for(item.endpoint) }}">
                     {% if item.icon %}
@@ -61,7 +61,7 @@
       <div class="col-md-6 d-flex justify-content-center">
         <small>
           <i class="ri-timer-2-line"></i> {{ g.request_time() }}
-          {%if config['DEBUG'].value and g.database_stats %}
+          {%if config['DEBUG'] and g.database_stats %}
           | <i class="ri-database-2-line"></i> {{g.database_stats.print() }}
           {% endif %}
         </small>
diff --git a/templates/index.html b/templates/index.html
index 2fdf6404..3e164276 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -3,8 +3,8 @@
 {% block main %}
 <div class="container-fluid">
   <h2 class="border-bottom lh-base pb-1">
-    <i class="ri-hashtag"></i> {% if config['RANDOM'].value %}Random selection of{% else %}Latest added{% endif %} sets
-    {% if not config['HIDE_ALL_SETS'].value %}
+    <i class="ri-hashtag"></i> {% if config['RANDOM'] %}Random selection of{% else %}Latest added{% endif %} sets
+    {% if not config['HIDE_ALL_SETS'] %}
       <a href="{{ url_for('set.list') }}" class="btn btn-sm btn-primary ms-1">All sets</a>
     {% endif %}
   </h2>
@@ -23,8 +23,8 @@
   {% endif %}
   {% if minifigure_collection | length %}
     <h2 class="border-bottom lh-base pb-1">
-      <i class="ri-group-line"></i> {% if config['RANDOM'].value %}Random selection of{% else %}Latest added{% endif %} minifigures
-      {% if not config['HIDE_ALL_MINIFIGURES'].value %}
+      <i class="ri-group-line"></i> {% if config['RANDOM'] %}Random selection of{% else %}Latest added{% endif %} minifigures
+      {% if not config['HIDE_ALL_MINIFIGURES'] %}
         <a href="{{ url_for('minifigure.list') }}" class="btn btn-sm btn-primary ms-1">All minifigures</a>
       {% endif %}
       </h2>
diff --git a/templates/instructions/upload.html b/templates/instructions/upload.html
index db877d87..f82fb2af 100644
--- a/templates/instructions/upload.html
+++ b/templates/instructions/upload.html
@@ -26,8 +26,8 @@
         <div class="mb-3">
           <label for="file" class="form-label">Instructions file</label>
           <div class="input-group">
-            <input type="file" class="form-control" id="file" name="file" accept="{{ ','.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}">
-            <span class="input-group-text">{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</span>
+            <input type="file" class="form-control" id="file" name="file" accept="{{ ','.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS']) }}">
+            <span class="input-group-text">{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS']) }}</span>
           </div>
         </div>
         <div class="text-end">
diff --git a/templates/macro/accordion.html b/templates/macro/accordion.html
index c622c86a..fbfd01b5 100644
--- a/templates/macro/accordion.html
+++ b/templates/macro/accordion.html
@@ -16,7 +16,7 @@
         {% endif %}
       </button>
     </h2>
-    <div id="{{ id }}" class="accordion-collapse collapse {% if expanded %}show{% endif %}" {% if not config['INDEPENDENT_ACCORDIONS'].value %}data-bs-parent="#{{ parent }}"{% endif %}>
+    <div id="{{ id }}" class="accordion-collapse collapse {% if expanded %}show{% endif %}" {% if not config['INDEPENDENT_ACCORDIONS'] %}data-bs-parent="#{{ parent }}"{% endif %}>
       <div class="accordion-body {% if class %}{{ class }}{% endif %}">
 {% endmacro %}
 
diff --git a/templates/macro/table.html b/templates/macro/table.html
index 396d8115..aa63eeed 100644
--- a/templates/macro/table.html
+++ b/templates/macro/table.html
@@ -60,7 +60,7 @@
           {% if number %}{ select: [{{ number }}], type: "number", searchable: false },{% endif %}
         ],
         pagerDelta: 1,
-        perPage: {{ config['DEFAULT_TABLE_PER_PAGE'].value }},
+        perPage: {{ config['DEFAULT_TABLE_PER_PAGE'] }},
         perPageSelect: [10, 25, 50, 100, 500, 1000],
         searchable: true,
         searchQuerySeparator: "",