We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Config.schema's are not applied if there are no configs in /opt/stackstorm/configs/
Hi,

This a odd one, just built a new 2.2 dev instance to finish up stackstorm-networking_utils#1 and I've found that the following schema for `networking_utils`:

```yaml
isp_db:
  description: "Path to ISP GeoIP2 database"
  type: "string"
  required: true
  default: "/opt/geoip2/GeoIP2-ISP.mmdb"
city_db:
  description: "Path to City GeoIP2 database"
  type: "string"
  required: true
  default: "/opt/geoip2/GeoLite2-City.mmdb"
```

Is only applied if at least 1 config file exists in `/opt/stackstorm/configs/` (for any pack). Without this I was key errors on `isp_db` in my `geoip` action.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `st2common/st2common/util/config_loader.py`
Content:
```
1 # Licensed to the StackStorm, Inc ('StackStorm') under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import copy
17 
18 import six
19 
20 from oslo_config import cfg
21 
22 from st2common import log as logging
23 from st2common.persistence.pack import ConfigSchema
24 from st2common.persistence.pack import Config
25 from st2common.content import utils as content_utils
26 from st2common.util import jinja as jinja_utils
27 from st2common.util.templating import render_template_with_system_and_user_context
28 from st2common.util.config_parser import ContentPackConfigParser
29 from st2common.exceptions.db import StackStormDBObjectNotFoundError
30 
31 __all__ = [
32     'ContentPackConfigLoader'
33 ]
34 
35 LOG = logging.getLogger(__name__)
36 
37 
38 class ContentPackConfigLoader(object):
39     """
40     Class which loads and resolves all the config values and returns a dictionary of resolved values
41     which can be passed to the resource.
42 
43     It loads and resolves values in the following order:
44 
45     1. Static values from <pack path>/config.yaml file
46     2. Dynamic and or static values from /opt/stackstorm/configs/<pack name>.yaml file.
47 
48     Values are merged from left to right which means values from "<pack name>.yaml" file have
49     precedence and override values from pack local config file.
50     """
51 
52     def __init__(self, pack_name, user=None):
53         self.pack_name = pack_name
54         self.user = user or cfg.CONF.system_user.user
55 
56         self.pack_path = content_utils.get_pack_base_path(pack_name=pack_name)
57         self._config_parser = ContentPackConfigParser(pack_name=pack_name)
58 
59     def get_config(self):
60         result = {}
61 
62         # 1. Retrieve values from pack local config.yaml file
63         config = self._config_parser.get_config()
64 
65         if config:
66             config = config.config or {}
67             result.update(config)
68 
69         # Retrieve corresponding ConfigDB and ConfigSchemaDB object
70         # Note: ConfigSchemaDB is optional right now. If it doesn't exist, we assume every value
71         # is of a type string
72         try:
73             config_db = Config.get_by_pack(value=self.pack_name)
74         except StackStormDBObjectNotFoundError:
75             # Corresponding pack config doesn't exist, return early
76             return result
77 
78         try:
79             config_schema_db = ConfigSchema.get_by_pack(value=self.pack_name)
80         except StackStormDBObjectNotFoundError:
81             config_schema_db = None
82 
83         # 2. Retrieve values from "global" pack config file (if available) and resolve them if
84         # necessary
85         config = self._get_values_for_config(config_schema_db=config_schema_db,
86                                              config_db=config_db)
87         result.update(config)
88 
89         return result
90 
91     def _get_values_for_config(self, config_schema_db, config_db):
92         schema_values = getattr(config_schema_db, 'attributes', {})
93         config_values = getattr(config_db, 'values', {})
94 
95         config = copy.deepcopy(config_values)
96 
97         # Assign dynamic config values based on the values in the datastore
98         config = self._assign_dynamic_config_values(schema=schema_values, config=config)
99 
100         # If config_schema is available we do a second pass and set default values for required
101         # items which values are not provided / available in the config itself
102         config = self._assign_default_values(schema=schema_values, config=config)
103         return config
104 
105     def _assign_dynamic_config_values(self, schema, config, parent_keys=None):
106         """
107         Assign dynamic config value for a particular config item if the ite utilizes a Jinja
108         expression for dynamic config values.
109 
110         Note: This method mutates config argument in place.
111 
112         :rtype: ``dict``
113         """
114         parent_keys = parent_keys or []
115 
116         for config_item_key, config_item_value in six.iteritems(config):
117             schema_item = schema.get(config_item_key, {})
118             is_dictionary = isinstance(config_item_value, dict)
119 
120             # Inspect nested object properties
121             if is_dictionary:
122                 parent_keys += [config_item_key]
123                 self._assign_dynamic_config_values(schema=schema_item.get('properties', {}),
124                                                    config=config[config_item_key],
125                                                    parent_keys=parent_keys)
126             else:
127                 is_jinja_expression = jinja_utils.is_jinja_expression(value=config_item_value)
128 
129                 if is_jinja_expression:
130                     # Resolve / render the Jinja template expression
131                     full_config_item_key = '.'.join(parent_keys + [config_item_key])
132                     value = self._get_datastore_value_for_expression(key=full_config_item_key,
133                         value=config_item_value,
134                         config_schema_item=schema_item)
135 
136                     config[config_item_key] = value
137                 else:
138                     # Static value, no resolution needed
139                     config[config_item_key] = config_item_value
140 
141         return config
142 
143     def _assign_default_values(self, schema, config):
144         """
145         Assign default values for particular config if default values are provided in the config
146         schema and a value is not specified in the config.
147 
148         Note: This method mutates config argument in place.
149 
150         :rtype: ``dict``
151         """
152         for schema_item_key, schema_item in six.iteritems(schema):
153             default_value = schema_item.get('default', None)
154             is_required = schema_item.get('required', False)
155             is_object = schema_item.get('type', None) == 'object'
156             has_properties = schema_item.get('properties', None)
157 
158             if is_required and default_value and not config.get(schema_item_key, None):
159                 config[schema_item_key] = default_value
160 
161             # Inspect nested object properties
162             if is_object and has_properties:
163                 if not config.get(schema_item_key, None):
164                     config[schema_item_key] = {}
165 
166                 self._assign_default_values(schema=schema_item['properties'],
167                                             config=config[schema_item_key])
168 
169         return config
170 
171     def _get_datastore_value_for_expression(self, key, value, config_schema_item=None):
172         """
173         Retrieve datastore value by first resolving the datastore expression and then retrieving
174         the value from the datastore.
175 
176         :param key: Full path to the config item key (e.g. "token" / "auth.settings.token", etc.)
177         """
178         from st2common.services.config import deserialize_key_value
179 
180         config_schema_item = config_schema_item or {}
181         secret = config_schema_item.get('secret', False)
182 
183         try:
184             value = render_template_with_system_and_user_context(value=value,
185                                                                  user=self.user)
186         except Exception as e:
187             # Throw a more user-friendly exception on failed render
188             exc_class = type(e)
189             original_msg = str(e)
190             msg = ('Failed to render dynamic configuration value for key "%s" with value '
191                    '"%s" for pack "%s" config: %s ' % (key, value, self.pack_name, original_msg))
192             raise exc_class(msg)
193 
194         if value:
195             # Deserialize the value
196             value = deserialize_key_value(value=value, secret=secret)
197         else:
198             value = None
199 
200         return value
201
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/st2common/st2common/util/config_loader.py b/st2common/st2common/util/config_loader.py
--- a/st2common/st2common/util/config_loader.py
+++ b/st2common/st2common/util/config_loader.py
@@ -20,6 +20,7 @@
 from oslo_config import cfg
 
 from st2common import log as logging
+from st2common.models.db.pack import ConfigDB
 from st2common.persistence.pack import ConfigSchema
 from st2common.persistence.pack import Config
 from st2common.content import utils as content_utils
@@ -72,8 +73,10 @@
         try:
             config_db = Config.get_by_pack(value=self.pack_name)
         except StackStormDBObjectNotFoundError:
-            # Corresponding pack config doesn't exist, return early
-            return result
+            # Corresponding pack config doesn't exist. We set config_db to an empty config so
+            # that the default values from config schema are still correctly applied even if
+            # pack doesn't contain a config.
+            config_db = ConfigDB(pack=self.pack_name, values={})
 
         try:
             config_schema_db = ConfigSchema.get_by_pack(value=self.pack_name)

{"golden_diff": "diff --git a/st2common/st2common/util/config_loader.py b/st2common/st2common/util/config_loader.py\n--- a/st2common/st2common/util/config_loader.py\n+++ b/st2common/st2common/util/config_loader.py\n@@ -20,6 +20,7 @@\n from oslo_config import cfg\n \n from st2common import log as logging\n+from st2common.models.db.pack import ConfigDB\n from st2common.persistence.pack import ConfigSchema\n from st2common.persistence.pack import Config\n from st2common.content import utils as content_utils\n@@ -72,8 +73,10 @@\n         try:\n             config_db = Config.get_by_pack(value=self.pack_name)\n         except StackStormDBObjectNotFoundError:\n-            # Corresponding pack config doesn't exist, return early\n-            return result\n+            # Corresponding pack config doesn't exist. We set config_db to an empty config so\n+            # that the default values from config schema are still correctly applied even if\n+            # pack doesn't contain a config.\n+            config_db = ConfigDB(pack=self.pack_name, values={})\n \n         try:\n             config_schema_db = ConfigSchema.get_by_pack(value=self.pack_name)\n", "issue": "Config.schema's are not applied if there are no configs in /opt/stackstorm/configs/\nHi,\r\n\r\nThis a odd one, just built a new 2.2 dev instance to finish up stackstorm-networking_utils#1 and I've found that the following schema for `networking_utils`:\r\n\r\n```yaml\r\nisp_db:\r\n  description: \"Path to ISP GeoIP2 database\"\r\n  type: \"string\"\r\n  required: true\r\n  default: \"/opt/geoip2/GeoIP2-ISP.mmdb\"\r\ncity_db:\r\n  description: \"Path to City GeoIP2 database\"\r\n  type: \"string\"\r\n  required: true\r\n  default: \"/opt/geoip2/GeoLite2-City.mmdb\"\r\n```\r\n\r\nIs only applied if at least 1 config file exists in `/opt/stackstorm/configs/` (for any pack). Without this I was key errors on `isp_db` in my `geoip` action.\n", "before_files": [{"content": "# Licensed to the StackStorm, Inc ('StackStorm') under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport copy\n\nimport six\n\nfrom oslo_config import cfg\n\nfrom st2common import log as logging\nfrom st2common.persistence.pack import ConfigSchema\nfrom st2common.persistence.pack import Config\nfrom st2common.content import utils as content_utils\nfrom st2common.util import jinja as jinja_utils\nfrom st2common.util.templating import render_template_with_system_and_user_context\nfrom st2common.util.config_parser import ContentPackConfigParser\nfrom st2common.exceptions.db import StackStormDBObjectNotFoundError\n\n__all__ = [\n    'ContentPackConfigLoader'\n]\n\nLOG = logging.getLogger(__name__)\n\n\nclass ContentPackConfigLoader(object):\n    \"\"\"\n    Class which loads and resolves all the config values and returns a dictionary of resolved values\n    which can be passed to the resource.\n\n    It loads and resolves values in the following order:\n\n    1. Static values from <pack path>/config.yaml file\n    2. Dynamic and or static values from /opt/stackstorm/configs/<pack name>.yaml file.\n\n    Values are merged from left to right which means values from \"<pack name>.yaml\" file have\n    precedence and override values from pack local config file.\n    \"\"\"\n\n    def __init__(self, pack_name, user=None):\n        self.pack_name = pack_name\n        self.user = user or cfg.CONF.system_user.user\n\n        self.pack_path = content_utils.get_pack_base_path(pack_name=pack_name)\n        self._config_parser = ContentPackConfigParser(pack_name=pack_name)\n\n    def get_config(self):\n        result = {}\n\n        # 1. Retrieve values from pack local config.yaml file\n        config = self._config_parser.get_config()\n\n        if config:\n            config = config.config or {}\n            result.update(config)\n\n        # Retrieve corresponding ConfigDB and ConfigSchemaDB object\n        # Note: ConfigSchemaDB is optional right now. If it doesn't exist, we assume every value\n        # is of a type string\n        try:\n            config_db = Config.get_by_pack(value=self.pack_name)\n        except StackStormDBObjectNotFoundError:\n            # Corresponding pack config doesn't exist, return early\n            return result\n\n        try:\n            config_schema_db = ConfigSchema.get_by_pack(value=self.pack_name)\n        except StackStormDBObjectNotFoundError:\n            config_schema_db = None\n\n        # 2. Retrieve values from \"global\" pack config file (if available) and resolve them if\n        # necessary\n        config = self._get_values_for_config(config_schema_db=config_schema_db,\n                                             config_db=config_db)\n        result.update(config)\n\n        return result\n\n    def _get_values_for_config(self, config_schema_db, config_db):\n        schema_values = getattr(config_schema_db, 'attributes', {})\n        config_values = getattr(config_db, 'values', {})\n\n        config = copy.deepcopy(config_values)\n\n        # Assign dynamic config values based on the values in the datastore\n        config = self._assign_dynamic_config_values(schema=schema_values, config=config)\n\n        # If config_schema is available we do a second pass and set default values for required\n        # items which values are not provided / available in the config itself\n        config = self._assign_default_values(schema=schema_values, config=config)\n        return config\n\n    def _assign_dynamic_config_values(self, schema, config, parent_keys=None):\n        \"\"\"\n        Assign dynamic config value for a particular config item if the ite utilizes a Jinja\n        expression for dynamic config values.\n\n        Note: This method mutates config argument in place.\n\n        :rtype: ``dict``\n        \"\"\"\n        parent_keys = parent_keys or []\n\n        for config_item_key, config_item_value in six.iteritems(config):\n            schema_item = schema.get(config_item_key, {})\n            is_dictionary = isinstance(config_item_value, dict)\n\n            # Inspect nested object properties\n            if is_dictionary:\n                parent_keys += [config_item_key]\n                self._assign_dynamic_config_values(schema=schema_item.get('properties', {}),\n                                                   config=config[config_item_key],\n                                                   parent_keys=parent_keys)\n            else:\n                is_jinja_expression = jinja_utils.is_jinja_expression(value=config_item_value)\n\n                if is_jinja_expression:\n                    # Resolve / render the Jinja template expression\n                    full_config_item_key = '.'.join(parent_keys + [config_item_key])\n                    value = self._get_datastore_value_for_expression(key=full_config_item_key,\n                        value=config_item_value,\n                        config_schema_item=schema_item)\n\n                    config[config_item_key] = value\n                else:\n                    # Static value, no resolution needed\n                    config[config_item_key] = config_item_value\n\n        return config\n\n    def _assign_default_values(self, schema, config):\n        \"\"\"\n        Assign default values for particular config if default values are provided in the config\n        schema and a value is not specified in the config.\n\n        Note: This method mutates config argument in place.\n\n        :rtype: ``dict``\n        \"\"\"\n        for schema_item_key, schema_item in six.iteritems(schema):\n            default_value = schema_item.get('default', None)\n            is_required = schema_item.get('required', False)\n            is_object = schema_item.get('type', None) == 'object'\n            has_properties = schema_item.get('properties', None)\n\n            if is_required and default_value and not config.get(schema_item_key, None):\n                config[schema_item_key] = default_value\n\n            # Inspect nested object properties\n            if is_object and has_properties:\n                if not config.get(schema_item_key, None):\n                    config[schema_item_key] = {}\n\n                self._assign_default_values(schema=schema_item['properties'],\n                                            config=config[schema_item_key])\n\n        return config\n\n    def _get_datastore_value_for_expression(self, key, value, config_schema_item=None):\n        \"\"\"\n        Retrieve datastore value by first resolving the datastore expression and then retrieving\n        the value from the datastore.\n\n        :param key: Full path to the config item key (e.g. \"token\" / \"auth.settings.token\", etc.)\n        \"\"\"\n        from st2common.services.config import deserialize_key_value\n\n        config_schema_item = config_schema_item or {}\n        secret = config_schema_item.get('secret', False)\n\n        try:\n            value = render_template_with_system_and_user_context(value=value,\n                                                                 user=self.user)\n        except Exception as e:\n            # Throw a more user-friendly exception on failed render\n            exc_class = type(e)\n            original_msg = str(e)\n            msg = ('Failed to render dynamic configuration value for key \"%s\" with value '\n                   '\"%s\" for pack \"%s\" config: %s ' % (key, value, self.pack_name, original_msg))\n            raise exc_class(msg)\n\n        if value:\n            # Deserialize the value\n            value = deserialize_key_value(value=value, secret=secret)\n        else:\n            value = None\n\n        return value\n", "path": "st2common/st2common/util/config_loader.py"}], "after_files": [{"content": "# Licensed to the StackStorm, Inc ('StackStorm') under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport copy\n\nimport six\n\nfrom oslo_config import cfg\n\nfrom st2common import log as logging\nfrom st2common.models.db.pack import ConfigDB\nfrom st2common.persistence.pack import ConfigSchema\nfrom st2common.persistence.pack import Config\nfrom st2common.content import utils as content_utils\nfrom st2common.util import jinja as jinja_utils\nfrom st2common.util.templating import render_template_with_system_and_user_context\nfrom st2common.util.config_parser import ContentPackConfigParser\nfrom st2common.exceptions.db import StackStormDBObjectNotFoundError\n\n__all__ = [\n    'ContentPackConfigLoader'\n]\n\nLOG = logging.getLogger(__name__)\n\n\nclass ContentPackConfigLoader(object):\n    \"\"\"\n    Class which loads and resolves all the config values and returns a dictionary of resolved values\n    which can be passed to the resource.\n\n    It loads and resolves values in the following order:\n\n    1. Static values from <pack path>/config.yaml file\n    2. Dynamic and or static values from /opt/stackstorm/configs/<pack name>.yaml file.\n\n    Values are merged from left to right which means values from \"<pack name>.yaml\" file have\n    precedence and override values from pack local config file.\n    \"\"\"\n\n    def __init__(self, pack_name, user=None):\n        self.pack_name = pack_name\n        self.user = user or cfg.CONF.system_user.user\n\n        self.pack_path = content_utils.get_pack_base_path(pack_name=pack_name)\n        self._config_parser = ContentPackConfigParser(pack_name=pack_name)\n\n    def get_config(self):\n        result = {}\n\n        # 1. Retrieve values from pack local config.yaml file\n        config = self._config_parser.get_config()\n\n        if config:\n            config = config.config or {}\n            result.update(config)\n\n        # Retrieve corresponding ConfigDB and ConfigSchemaDB object\n        # Note: ConfigSchemaDB is optional right now. If it doesn't exist, we assume every value\n        # is of a type string\n        try:\n            config_db = Config.get_by_pack(value=self.pack_name)\n        except StackStormDBObjectNotFoundError:\n            # Corresponding pack config doesn't exist. We set config_db to an empty config so\n            # that the default values from config schema are still correctly applied even if\n            # pack doesn't contain a config.\n            config_db = ConfigDB(pack=self.pack_name, values={})\n\n        try:\n            config_schema_db = ConfigSchema.get_by_pack(value=self.pack_name)\n        except StackStormDBObjectNotFoundError:\n            config_schema_db = None\n\n        # 2. Retrieve values from \"global\" pack config file (if available) and resolve them if\n        # necessary\n        config = self._get_values_for_config(config_schema_db=config_schema_db,\n                                             config_db=config_db)\n        result.update(config)\n\n        return result\n\n    def _get_values_for_config(self, config_schema_db, config_db):\n        schema_values = getattr(config_schema_db, 'attributes', {})\n        config_values = getattr(config_db, 'values', {})\n\n        config = copy.deepcopy(config_values)\n\n        # Assign dynamic config values based on the values in the datastore\n        config = self._assign_dynamic_config_values(schema=schema_values, config=config)\n\n        # If config_schema is available we do a second pass and set default values for required\n        # items which values are not provided / available in the config itself\n        config = self._assign_default_values(schema=schema_values, config=config)\n        return config\n\n    def _assign_dynamic_config_values(self, schema, config, parent_keys=None):\n        \"\"\"\n        Assign dynamic config value for a particular config item if the ite utilizes a Jinja\n        expression for dynamic config values.\n\n        Note: This method mutates config argument in place.\n\n        :rtype: ``dict``\n        \"\"\"\n        parent_keys = parent_keys or []\n\n        for config_item_key, config_item_value in six.iteritems(config):\n            schema_item = schema.get(config_item_key, {})\n            is_dictionary = isinstance(config_item_value, dict)\n\n            # Inspect nested object properties\n            if is_dictionary:\n                parent_keys += [config_item_key]\n                self._assign_dynamic_config_values(schema=schema_item.get('properties', {}),\n                                                   config=config[config_item_key],\n                                                   parent_keys=parent_keys)\n            else:\n                is_jinja_expression = jinja_utils.is_jinja_expression(value=config_item_value)\n\n                if is_jinja_expression:\n                    # Resolve / render the Jinja template expression\n                    full_config_item_key = '.'.join(parent_keys + [config_item_key])\n                    value = self._get_datastore_value_for_expression(key=full_config_item_key,\n                        value=config_item_value,\n                        config_schema_item=schema_item)\n\n                    config[config_item_key] = value\n                else:\n                    # Static value, no resolution needed\n                    config[config_item_key] = config_item_value\n\n        return config\n\n    def _assign_default_values(self, schema, config):\n        \"\"\"\n        Assign default values for particular config if default values are provided in the config\n        schema and a value is not specified in the config.\n\n        Note: This method mutates config argument in place.\n\n        :rtype: ``dict``\n        \"\"\"\n        for schema_item_key, schema_item in six.iteritems(schema):\n            default_value = schema_item.get('default', None)\n            is_required = schema_item.get('required', False)\n            is_object = schema_item.get('type', None) == 'object'\n            has_properties = schema_item.get('properties', None)\n\n            if is_required and default_value and not config.get(schema_item_key, None):\n                config[schema_item_key] = default_value\n\n            # Inspect nested object properties\n            if is_object and has_properties:\n                if not config.get(schema_item_key, None):\n                    config[schema_item_key] = {}\n\n                self._assign_default_values(schema=schema_item['properties'],\n                                            config=config[schema_item_key])\n\n        return config\n\n    def _get_datastore_value_for_expression(self, key, value, config_schema_item=None):\n        \"\"\"\n        Retrieve datastore value by first resolving the datastore expression and then retrieving\n        the value from the datastore.\n\n        :param key: Full path to the config item key (e.g. \"token\" / \"auth.settings.token\", etc.)\n        \"\"\"\n        from st2common.services.config import deserialize_key_value\n\n        config_schema_item = config_schema_item or {}\n        secret = config_schema_item.get('secret', False)\n\n        try:\n            value = render_template_with_system_and_user_context(value=value,\n                                                                 user=self.user)\n        except Exception as e:\n            # Throw a more user-friendly exception on failed render\n            exc_class = type(e)\n            original_msg = str(e)\n            msg = ('Failed to render dynamic configuration value for key \"%s\" with value '\n                   '\"%s\" for pack \"%s\" config: %s ' % (key, value, self.pack_name, original_msg))\n            raise exc_class(msg)\n\n        if value:\n            # Deserialize the value\n            value = deserialize_key_value(value=value, secret=secret)\n        else:\n            value = None\n\n        return value\n", "path": "st2common/st2common/util/config_loader.py"}]}