We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Default values in pack config schema not being honored
If a pack has a schema that declares a default value for a specific property and the property is absent in the config instance (`/opt/stackstorm/configs/<pack>.yaml`), then a python runner attempts to read this value from the config a `KeyError` is thrown instead of the default value being returned.

# Steps to reproduce
I've created a test pack that demonstrates this: https://github.com/nmaludy/stackstorm-testconfigdefault
```
st2 pack install https://github.com/nmaludy/stackstorm-testconfigdefault
st2 run testconfigdefault.get_config key='output'
```

# Output:
```
[root@stackstorm ~]# st2 run testconfigdefault.get_config key='output'
.
id: 593fdecfa814c06997fc8fd4
status: failed
parameters: 
  key: output
result: 
  exit_code: 1
  result: None
  stderr: "Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py", line 259, in <module>
    obj.run()
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py", line 155, in run
    output = action.run(**self._parameters)
  File "/opt/stackstorm/packs/testconfigdefault/actions/get_config.py", line 11, in run
    return self.config[key]
KeyError: u'output'
"
  stdout: ''
```

# Expected output:
I expect to have the default value of 'json' from the schema be returned. Maybe i'm incorrect in my assumption?


--- 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.models.db.pack import ConfigDB
24 from st2common.persistence.pack import ConfigSchema
25 from st2common.persistence.pack import Config
26 from st2common.content import utils as content_utils
27 from st2common.util import jinja as jinja_utils
28 from st2common.util.templating import render_template_with_system_and_user_context
29 from st2common.util.config_parser import ContentPackConfigParser
30 from st2common.exceptions.db import StackStormDBObjectNotFoundError
31 
32 __all__ = [
33     'ContentPackConfigLoader'
34 ]
35 
36 LOG = logging.getLogger(__name__)
37 
38 
39 class ContentPackConfigLoader(object):
40     """
41     Class which loads and resolves all the config values and returns a dictionary of resolved values
42     which can be passed to the resource.
43 
44     It loads and resolves values in the following order:
45 
46     1. Static values from <pack path>/config.yaml file
47     2. Dynamic and or static values from /opt/stackstorm/configs/<pack name>.yaml file.
48 
49     Values are merged from left to right which means values from "<pack name>.yaml" file have
50     precedence and override values from pack local config file.
51     """
52 
53     def __init__(self, pack_name, user=None):
54         self.pack_name = pack_name
55         self.user = user or cfg.CONF.system_user.user
56 
57         self.pack_path = content_utils.get_pack_base_path(pack_name=pack_name)
58         self._config_parser = ContentPackConfigParser(pack_name=pack_name)
59 
60     def get_config(self):
61         result = {}
62 
63         # 1. Retrieve values from pack local config.yaml file
64         config = self._config_parser.get_config()
65 
66         if config:
67             config = config.config or {}
68             result.update(config)
69 
70         # Retrieve corresponding ConfigDB and ConfigSchemaDB object
71         # Note: ConfigSchemaDB is optional right now. If it doesn't exist, we assume every value
72         # is of a type string
73         try:
74             config_db = Config.get_by_pack(value=self.pack_name)
75         except StackStormDBObjectNotFoundError:
76             # Corresponding pack config doesn't exist. We set config_db to an empty config so
77             # that the default values from config schema are still correctly applied even if
78             # pack doesn't contain a config.
79             config_db = ConfigDB(pack=self.pack_name, values={})
80 
81         try:
82             config_schema_db = ConfigSchema.get_by_pack(value=self.pack_name)
83         except StackStormDBObjectNotFoundError:
84             config_schema_db = None
85 
86         # 2. Retrieve values from "global" pack config file (if available) and resolve them if
87         # necessary
88         config = self._get_values_for_config(config_schema_db=config_schema_db,
89                                              config_db=config_db)
90         result.update(config)
91 
92         return result
93 
94     def _get_values_for_config(self, config_schema_db, config_db):
95         schema_values = getattr(config_schema_db, 'attributes', {})
96         config_values = getattr(config_db, 'values', {})
97 
98         config = copy.deepcopy(config_values)
99 
100         # Assign dynamic config values based on the values in the datastore
101         config = self._assign_dynamic_config_values(schema=schema_values, config=config)
102 
103         # If config_schema is available we do a second pass and set default values for required
104         # items which values are not provided / available in the config itself
105         config = self._assign_default_values(schema=schema_values, config=config)
106         return config
107 
108     def _assign_dynamic_config_values(self, schema, config, parent_keys=None):
109         """
110         Assign dynamic config value for a particular config item if the ite utilizes a Jinja
111         expression for dynamic config values.
112 
113         Note: This method mutates config argument in place.
114 
115         :rtype: ``dict``
116         """
117         parent_keys = parent_keys or []
118 
119         for config_item_key, config_item_value in six.iteritems(config):
120             schema_item = schema.get(config_item_key, {})
121             is_dictionary = isinstance(config_item_value, dict)
122 
123             # Inspect nested object properties
124             if is_dictionary:
125                 parent_keys += [config_item_key]
126                 self._assign_dynamic_config_values(schema=schema_item.get('properties', {}),
127                                                    config=config[config_item_key],
128                                                    parent_keys=parent_keys)
129             else:
130                 is_jinja_expression = jinja_utils.is_jinja_expression(value=config_item_value)
131 
132                 if is_jinja_expression:
133                     # Resolve / render the Jinja template expression
134                     full_config_item_key = '.'.join(parent_keys + [config_item_key])
135                     value = self._get_datastore_value_for_expression(key=full_config_item_key,
136                         value=config_item_value,
137                         config_schema_item=schema_item)
138 
139                     config[config_item_key] = value
140                 else:
141                     # Static value, no resolution needed
142                     config[config_item_key] = config_item_value
143 
144         return config
145 
146     def _assign_default_values(self, schema, config):
147         """
148         Assign default values for particular config if default values are provided in the config
149         schema and a value is not specified in the config.
150 
151         Note: This method mutates config argument in place.
152 
153         :rtype: ``dict``
154         """
155         for schema_item_key, schema_item in six.iteritems(schema):
156             default_value = schema_item.get('default', None)
157             is_required = schema_item.get('required', False)
158             is_object = schema_item.get('type', None) == 'object'
159             has_properties = schema_item.get('properties', None)
160 
161             if is_required and default_value and not config.get(schema_item_key, None):
162                 config[schema_item_key] = default_value
163 
164             # Inspect nested object properties
165             if is_object and has_properties:
166                 if not config.get(schema_item_key, None):
167                     config[schema_item_key] = {}
168 
169                 self._assign_default_values(schema=schema_item['properties'],
170                                             config=config[schema_item_key])
171 
172         return config
173 
174     def _get_datastore_value_for_expression(self, key, value, config_schema_item=None):
175         """
176         Retrieve datastore value by first resolving the datastore expression and then retrieving
177         the value from the datastore.
178 
179         :param key: Full path to the config item key (e.g. "token" / "auth.settings.token", etc.)
180         """
181         from st2common.services.config import deserialize_key_value
182 
183         config_schema_item = config_schema_item or {}
184         secret = config_schema_item.get('secret', False)
185 
186         try:
187             value = render_template_with_system_and_user_context(value=value,
188                                                                  user=self.user)
189         except Exception as e:
190             # Throw a more user-friendly exception on failed render
191             exc_class = type(e)
192             original_msg = str(e)
193             msg = ('Failed to render dynamic configuration value for key "%s" with value '
194                    '"%s" for pack "%s" config: %s ' % (key, value, self.pack_name, original_msg))
195             raise exc_class(msg)
196 
197         if value:
198             # Deserialize the value
199             value = deserialize_key_value(value=value, secret=secret)
200         else:
201             value = None
202 
203         return value
204
```
--- 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
@@ -154,11 +154,10 @@
         """
         for schema_item_key, schema_item in six.iteritems(schema):
             default_value = schema_item.get('default', None)
-            is_required = schema_item.get('required', False)
             is_object = schema_item.get('type', None) == 'object'
             has_properties = schema_item.get('properties', None)
 
-            if is_required and default_value and not config.get(schema_item_key, None):
+            if default_value and not config.get(schema_item_key, None):
                 config[schema_item_key] = default_value
 
             # Inspect nested object properties

{"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@@ -154,11 +154,10 @@\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+            if default_value and not config.get(schema_item_key, None):\n                 config[schema_item_key] = default_value\n \n             # Inspect nested object properties\n", "issue": "Default values in pack config schema not being honored\nIf a pack has a schema that declares a default value for a specific property and the property is absent in the config instance (`/opt/stackstorm/configs/<pack>.yaml`), then a python runner attempts to read this value from the config a `KeyError` is thrown instead of the default value being returned.\r\n\r\n# Steps to reproduce\r\nI've created a test pack that demonstrates this: https://github.com/nmaludy/stackstorm-testconfigdefault\r\n```\r\nst2 pack install https://github.com/nmaludy/stackstorm-testconfigdefault\r\nst2 run testconfigdefault.get_config key='output'\r\n```\r\n\r\n# Output:\r\n```\r\n[root@stackstorm ~]# st2 run testconfigdefault.get_config key='output'\r\n.\r\nid: 593fdecfa814c06997fc8fd4\r\nstatus: failed\r\nparameters: \r\n  key: output\r\nresult: \r\n  exit_code: 1\r\n  result: None\r\n  stderr: \"Traceback (most recent call last):\r\n  File \"/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py\", line 259, in <module>\r\n    obj.run()\r\n  File \"/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py\", line 155, in run\r\n    output = action.run(**self._parameters)\r\n  File \"/opt/stackstorm/packs/testconfigdefault/actions/get_config.py\", line 11, in run\r\n    return self.config[key]\r\nKeyError: u'output'\r\n\"\r\n  stdout: ''\r\n```\r\n\r\n# Expected output:\r\nI expect to have the default value of 'json' from the schema be returned. Maybe i'm incorrect in my assumption?\r\n\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.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"}], "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_object = schema_item.get('type', None) == 'object'\n            has_properties = schema_item.get('properties', None)\n\n            if 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"}]}