We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
action alias regex fails to evaluate null optional arguments when type is not string
For example, something like:  `update {{hostname}}( {{count}} times)?` 
Works for `!update myhost 2 times`
Fails for `!update myhost`
Error: `(invalid literal for int() with base 10: '')`
So the workaround is to change the type of `count` from integer to string.
Or @emedvedev provides another workaround:

```
- update {{ hostname }} {{ count }} times
- update {{ hostname }}
```

Start from the most explicit.


--- 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/models/utils/action_alias_utils.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 re
17 from st2common.exceptions import content
18 
19 __all__ = [
20     'ActionAliasFormatParser'
21 ]
22 
23 
24 class ActionAliasFormatParser(object):
25 
26     def __init__(self, alias_format=None, param_stream=None):
27         self._format = alias_format or ''
28         self._param_stream = param_stream or ''
29 
30     def get_extracted_param_value(self):
31 
32         result = {}
33 
34         # As there's a lot of questions about using regular expressions,
35         # I'll try to be thorough when documenting this code.
36 
37         # We're parsing the arbitrary key-value pairs at the end of the stream
38         # to support passing of parameters not specified in the format string,
39         # and cutting them from the stream as they're no longer needed.
40         # Possible values are quoted strings, a word, or anything inside "{}".
41         pairs_match = r'(?:^|\s+)(\S+)=("(.*?)"|\'(.*?)\'|({.*?})|(\S+))'
42         extra = re.match(r'.*?((' + pairs_match + r'\s*)*)$',
43                          self._param_stream, re.DOTALL)
44         if extra:
45             kv_pairs = re.findall(pairs_match,
46                                   extra.group(1), re.DOTALL)
47             self._param_stream = self._param_stream.replace(extra.group(1), '')
48         self._param_stream = " %s " % self._param_stream
49 
50         # Now we'll match parameters with default values in form of
51         # {{ value = parameter }} (and all possible permutations of spaces),
52         # compiling them into a list.
53         # "test {{ url = http://google.com }} {{ extra = Test }}" will become
54         # [ ["url", "http://google.com"], ["extra", "Test"] ]
55         params = re.findall(r'{{\s*(.+?)\s*(?:=\s*[\'"]?({.+?}|.+?)[\'"]?)?\s*}}',
56                             self._format, re.DOTALL)
57 
58         # Now we're transforming our format string into a regular expression,
59         # substituting {{ ... }} with regex named groups, so that param_stream
60         # matched against this expression yields a dict of params with values.
61         param_match = r'["\']?(?P<\2>(?:(?<=\').+?(?=\')|(?<=").+?(?=")|{.+?}|.+?))["\']?'
62         reg = re.sub(r'(\s*){{\s*([^=}]+?)\s*}}(?![\'"]?\s+}})',
63                      r'\1' + param_match,
64                      self._format)
65         reg = re.sub(r'(\s*){{\s*(\S+)\s*=\s*(?:{.+?}|.+?)\s*}}',
66                      r'(?:\1' + param_match + r')?',
67                      reg)
68         reg = re.sub(r'(\s*){{\s*(.+?)\s*}}',
69                      r'\1' + param_match,
70                      reg)
71         reg = '^\s*' + reg + r'\s*$'
72 
73         # Now we're matching param_stream against our format string regex,
74         # getting a dict of values. We'll also get default values from
75         # "params" list if something is not present.
76         # Priority, from lowest to highest:
77         # 1. Default parameters
78         # 2. Matched parameters
79         # 3. Extra parameters
80         matched_stream = re.match(reg, self._param_stream, re.DOTALL)
81         if matched_stream:
82             values = matched_stream.groupdict()
83         for param in params:
84             matched_value = values[param[0]] if matched_stream else None
85             result[param[0]] = matched_value or param[1]
86         if extra:
87             for pair in kv_pairs:
88                 result[pair[0]] = ''.join(pair[2:])
89 
90         if self._format and not (self._param_stream.strip() or any(result.values())):
91             raise content.ParseException('No value supplied and no default value found.')
92 
93         return result
94
```
--- 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/models/utils/action_alias_utils.py b/st2common/st2common/models/utils/action_alias_utils.py
--- a/st2common/st2common/models/utils/action_alias_utils.py
+++ b/st2common/st2common/models/utils/action_alias_utils.py
@@ -82,7 +82,9 @@
             values = matched_stream.groupdict()
         for param in params:
             matched_value = values[param[0]] if matched_stream else None
-            result[param[0]] = matched_value or param[1]
+            matched_result = matched_value or param[1]
+            if matched_result:
+                result[param[0]] = matched_result
         if extra:
             for pair in kv_pairs:
                 result[pair[0]] = ''.join(pair[2:])

{"golden_diff": "diff --git a/st2common/st2common/models/utils/action_alias_utils.py b/st2common/st2common/models/utils/action_alias_utils.py\n--- a/st2common/st2common/models/utils/action_alias_utils.py\n+++ b/st2common/st2common/models/utils/action_alias_utils.py\n@@ -82,7 +82,9 @@\n             values = matched_stream.groupdict()\n         for param in params:\n             matched_value = values[param[0]] if matched_stream else None\n-            result[param[0]] = matched_value or param[1]\n+            matched_result = matched_value or param[1]\n+            if matched_result:\n+                result[param[0]] = matched_result\n         if extra:\n             for pair in kv_pairs:\n                 result[pair[0]] = ''.join(pair[2:])\n", "issue": "action alias regex fails to evaluate null optional arguments when type is not string\nFor example, something like:  `update {{hostname}}( {{count}} times)?` \nWorks for `!update myhost 2 times`\nFails for `!update myhost`\nError: `(invalid literal for int() with base 10: '')`\nSo the workaround is to change the type of `count` from integer to string.\nOr @emedvedev provides another workaround:\n\n```\n- update {{ hostname }} {{ count }} times\n- update {{ hostname }}\n```\n\nStart from the most explicit.\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 re\nfrom st2common.exceptions import content\n\n__all__ = [\n    'ActionAliasFormatParser'\n]\n\n\nclass ActionAliasFormatParser(object):\n\n    def __init__(self, alias_format=None, param_stream=None):\n        self._format = alias_format or ''\n        self._param_stream = param_stream or ''\n\n    def get_extracted_param_value(self):\n\n        result = {}\n\n        # As there's a lot of questions about using regular expressions,\n        # I'll try to be thorough when documenting this code.\n\n        # We're parsing the arbitrary key-value pairs at the end of the stream\n        # to support passing of parameters not specified in the format string,\n        # and cutting them from the stream as they're no longer needed.\n        # Possible values are quoted strings, a word, or anything inside \"{}\".\n        pairs_match = r'(?:^|\\s+)(\\S+)=(\"(.*?)\"|\\'(.*?)\\'|({.*?})|(\\S+))'\n        extra = re.match(r'.*?((' + pairs_match + r'\\s*)*)$',\n                         self._param_stream, re.DOTALL)\n        if extra:\n            kv_pairs = re.findall(pairs_match,\n                                  extra.group(1), re.DOTALL)\n            self._param_stream = self._param_stream.replace(extra.group(1), '')\n        self._param_stream = \" %s \" % self._param_stream\n\n        # Now we'll match parameters with default values in form of\n        # {{ value = parameter }} (and all possible permutations of spaces),\n        # compiling them into a list.\n        # \"test {{ url = http://google.com }} {{ extra = Test }}\" will become\n        # [ [\"url\", \"http://google.com\"], [\"extra\", \"Test\"] ]\n        params = re.findall(r'{{\\s*(.+?)\\s*(?:=\\s*[\\'\"]?({.+?}|.+?)[\\'\"]?)?\\s*}}',\n                            self._format, re.DOTALL)\n\n        # Now we're transforming our format string into a regular expression,\n        # substituting {{ ... }} with regex named groups, so that param_stream\n        # matched against this expression yields a dict of params with values.\n        param_match = r'[\"\\']?(?P<\\2>(?:(?<=\\').+?(?=\\')|(?<=\").+?(?=\")|{.+?}|.+?))[\"\\']?'\n        reg = re.sub(r'(\\s*){{\\s*([^=}]+?)\\s*}}(?![\\'\"]?\\s+}})',\n                     r'\\1' + param_match,\n                     self._format)\n        reg = re.sub(r'(\\s*){{\\s*(\\S+)\\s*=\\s*(?:{.+?}|.+?)\\s*}}',\n                     r'(?:\\1' + param_match + r')?',\n                     reg)\n        reg = re.sub(r'(\\s*){{\\s*(.+?)\\s*}}',\n                     r'\\1' + param_match,\n                     reg)\n        reg = '^\\s*' + reg + r'\\s*$'\n\n        # Now we're matching param_stream against our format string regex,\n        # getting a dict of values. We'll also get default values from\n        # \"params\" list if something is not present.\n        # Priority, from lowest to highest:\n        # 1. Default parameters\n        # 2. Matched parameters\n        # 3. Extra parameters\n        matched_stream = re.match(reg, self._param_stream, re.DOTALL)\n        if matched_stream:\n            values = matched_stream.groupdict()\n        for param in params:\n            matched_value = values[param[0]] if matched_stream else None\n            result[param[0]] = matched_value or param[1]\n        if extra:\n            for pair in kv_pairs:\n                result[pair[0]] = ''.join(pair[2:])\n\n        if self._format and not (self._param_stream.strip() or any(result.values())):\n            raise content.ParseException('No value supplied and no default value found.')\n\n        return result\n", "path": "st2common/st2common/models/utils/action_alias_utils.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 re\nfrom st2common.exceptions import content\n\n__all__ = [\n    'ActionAliasFormatParser'\n]\n\n\nclass ActionAliasFormatParser(object):\n\n    def __init__(self, alias_format=None, param_stream=None):\n        self._format = alias_format or ''\n        self._param_stream = param_stream or ''\n\n    def get_extracted_param_value(self):\n\n        result = {}\n\n        # As there's a lot of questions about using regular expressions,\n        # I'll try to be thorough when documenting this code.\n\n        # We're parsing the arbitrary key-value pairs at the end of the stream\n        # to support passing of parameters not specified in the format string,\n        # and cutting them from the stream as they're no longer needed.\n        # Possible values are quoted strings, a word, or anything inside \"{}\".\n        pairs_match = r'(?:^|\\s+)(\\S+)=(\"(.*?)\"|\\'(.*?)\\'|({.*?})|(\\S+))'\n        extra = re.match(r'.*?((' + pairs_match + r'\\s*)*)$',\n                         self._param_stream, re.DOTALL)\n        if extra:\n            kv_pairs = re.findall(pairs_match,\n                                  extra.group(1), re.DOTALL)\n            self._param_stream = self._param_stream.replace(extra.group(1), '')\n        self._param_stream = \" %s \" % self._param_stream\n\n        # Now we'll match parameters with default values in form of\n        # {{ value = parameter }} (and all possible permutations of spaces),\n        # compiling them into a list.\n        # \"test {{ url = http://google.com }} {{ extra = Test }}\" will become\n        # [ [\"url\", \"http://google.com\"], [\"extra\", \"Test\"] ]\n        params = re.findall(r'{{\\s*(.+?)\\s*(?:=\\s*[\\'\"]?({.+?}|.+?)[\\'\"]?)?\\s*}}',\n                            self._format, re.DOTALL)\n\n        # Now we're transforming our format string into a regular expression,\n        # substituting {{ ... }} with regex named groups, so that param_stream\n        # matched against this expression yields a dict of params with values.\n        param_match = r'[\"\\']?(?P<\\2>(?:(?<=\\').+?(?=\\')|(?<=\").+?(?=\")|{.+?}|.+?))[\"\\']?'\n        reg = re.sub(r'(\\s*){{\\s*([^=}]+?)\\s*}}(?![\\'\"]?\\s+}})',\n                     r'\\1' + param_match,\n                     self._format)\n        reg = re.sub(r'(\\s*){{\\s*(\\S+)\\s*=\\s*(?:{.+?}|.+?)\\s*}}',\n                     r'(?:\\1' + param_match + r')?',\n                     reg)\n        reg = re.sub(r'(\\s*){{\\s*(.+?)\\s*}}',\n                     r'\\1' + param_match,\n                     reg)\n        reg = '^\\s*' + reg + r'\\s*$'\n\n        # Now we're matching param_stream against our format string regex,\n        # getting a dict of values. We'll also get default values from\n        # \"params\" list if something is not present.\n        # Priority, from lowest to highest:\n        # 1. Default parameters\n        # 2. Matched parameters\n        # 3. Extra parameters\n        matched_stream = re.match(reg, self._param_stream, re.DOTALL)\n        if matched_stream:\n            values = matched_stream.groupdict()\n        for param in params:\n            matched_value = values[param[0]] if matched_stream else None\n            matched_result = matched_value or param[1]\n            if matched_result:\n                result[param[0]] = matched_result\n        if extra:\n            for pair in kv_pairs:\n                result[pair[0]] = ''.join(pair[2:])\n\n        if self._format and not (self._param_stream.strip() or any(result.values())):\n            raise content.ParseException('No value supplied and no default value found.')\n\n        return result\n", "path": "st2common/st2common/models/utils/action_alias_utils.py"}]}