Repository: aws-cloudformation/cloudformation-cli

Issue:
The pypi release github action needs to be updated
```
 You are using "pypa/gh-action-pypi-publish@master". The "master" branch
of this project has been sunset and will not receive any updates, not even
security bug fixes. Please, make sure to use a supported version.  If you want
to pin to v1 major version, use "pypa/gh-action-pypi-publish@release/v1". If
you feel adventurous, you may opt to use use 
"pypa/gh-action-pypi-publish@unstable/v1" instead. A more general recommendation
is to pin to exact tags or commit shas.
```


Discussion:

Patch:
diff --git a/.github/workflows/pypi-release.yaml b/.github/workflows/pypi-release.yaml
index b893ac7..4a56df5 100644
--- a/.github/workflows/pypi-release.yaml
+++ b/.github/workflows/pypi-release.yaml
@@ -22,6 +22,6 @@ jobs:
       run: |
         python setup.py sdist bdist_wheel
     - name: Publish distribution 📦 to PyPI (If triggered from release)
-      uses: pypa/gh-action-pypi-publish@master
+      uses: pypa/gh-action-pypi-publish@release/v1
       with:
         password: ${{ secrets.PYPI_API_KEY_CLOUDFORMATION_CLI }}
diff --git a/src/rpdk/core/contract/resource_generator.py b/src/rpdk/core/contract/resource_generator.py
index 7818d70..ad54533 100644
--- a/src/rpdk/core/contract/resource_generator.py
+++ b/src/rpdk/core/contract/resource_generator.py
@@ -1,4 +1,5 @@
 import logging
+import re
 from collections.abc import Sequence
 
 from hypothesis.strategies import (
@@ -27,9 +28,17 @@ LOG = logging.getLogger(__name__)
 # https://github.com/aws-cloudformation/aws-cloudformation-rpdk/issues/118
 
 # Arn is just a placeholder for testing
+# format list taken from https://python-jsonschema.readthedocs.io/en/stable/validate/#jsonschema.FormatChecker.checkers
+# date-time regex from https://github.com/naimetti/rfc3339-validator
+# date is extraction from date-time
+# time is extraction from date-time
 STRING_FORMATS = {
     "arn": "^arn:aws(-(cn|gov))?:[a-z-]+:(([a-z]+-)+[0-9])?:([0-9]{12})?:[^.]+$",
-    "uri": "^(https?|ftp|file)://[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])(:[0-9]*)*([?/#].*)?$",
+    "uri": r"^(https?|ftp|file)://[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])(:[0-9]*)*([?/#].*)?$",
+    "date-time": r"^(\d{4})-(0[1-9]|1[0-2])-(\d{2})T(?:[01]\d|2[0123]):(?:[0-5]\d):(?:[0-5]\d)(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0123]):[0-5]\d)$",
+    "date": r"^(\d{4})-(0[1-9]|1[0-2])-(\d{2})$",
+    "time": r"^(?:[01]\d|2[0123]):(?:[0-5]\d):(?:[0-5]\d)(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0123]):[0-5]\d)$",
+    "email": r"^.+@[^\.].*\.[a-z]{2,}$",
 }
 
 NEG_INF = float("-inf")
@@ -37,8 +46,10 @@ POS_INF = float("inf")
 
 
 def terminate_regex(regex):
+    if regex.startswith("^"):
+        regex = r"\A" + regex[1:]
     if regex.endswith("$"):
-        return regex[:-1] + r"\Z"
+        regex = regex[:-1] + r"\Z"
     return regex
 
 
@@ -247,7 +258,7 @@ class ResourceGenerator:
             if "maxLength" in schema:  # pragma: no cover
                 LOG.warning("found maxLength used with pattern")
 
-            return from_regex(terminate_regex(regex))
+            return from_regex(re.compile(terminate_regex(regex), re.ASCII))
 
         if "pattern" in schema:  # pragma: no cover
             LOG.warning("found pattern used with format")
@@ -257,4 +268,4 @@ class ResourceGenerator:
             LOG.warning("found maxLength used with format")
 
         regex = STRING_FORMATS[string_format]
-        return from_regex(regex)
+        return from_regex(re.compile(regex, re.ASCII))
diff --git a/src/rpdk/core/data_loaders.py b/src/rpdk/core/data_loaders.py
index c4a6fd1..61f36f7 100644
--- a/src/rpdk/core/data_loaders.py
+++ b/src/rpdk/core/data_loaders.py
@@ -249,7 +249,10 @@ def load_resource_spec(resource_spec_file):  # pylint: disable=R # noqa: C901
                 pattern,
             )
         try:
-            re.compile(pattern)
+            # http://json-schema.org/understanding-json-schema/reference/regular_expressions.html
+            # ECMA-262 has \w, \W, \b, \B, \d, \D, \s and \S perform ASCII-only matching
+            # instead of full Unicode matching. Unicode matching is the default in Python
+            re.compile(pattern, re.ASCII)
         except re.error:
             LOG.warning("Could not validate regular expression: %s", pattern)
 


Test patch:
diff --git a/tests/contract/test_resource_generator.py b/tests/contract/test_resource_generator.py
index c02f34a..0ba9ea8 100644
--- a/tests/contract/test_resource_generator.py
+++ b/tests/contract/test_resource_generator.py
@@ -22,9 +22,28 @@ def test_terminate_regex_end_of_line_like_a_normal_person():
     assert re.match(modified_regex, "dfqh3eqefhq")
 
 
-def test_terminate_regex_no_termination_needed():
+def test_terminate_regex_line_start_change():
     original_regex = r"^[a-zA-Z0-9]{1,219}\Z"
-    assert terminate_regex(original_regex) == original_regex
+    terminated_regex = r"\A[a-zA-Z0-9]{1,219}\Z"
+    assert terminate_regex(original_regex) == terminated_regex
+
+
+def test_terminate_regex_line_end_change():
+    original_regex = r"\A[a-zA-Z0-9]{1,219}$"
+    terminated_regex = r"\A[a-zA-Z0-9]{1,219}\Z"
+    assert terminate_regex(original_regex) == terminated_regex
+
+
+def test_terminate_regex_line_start_and_end_change():
+    original_regex = r"^[a-zA-Z0-9]{1,219}$"
+    terminated_regex = r"\A[a-zA-Z0-9]{1,219}\Z"
+    assert terminate_regex(original_regex) == terminated_regex
+
+
+def test_terminate_regex_no_termination_needed():
+    original_regex = r"\A[a-zA-Z0-9]{1,219}\Z"
+    terminated_regex = r"\A[a-zA-Z0-9]{1,219}\Z"
+    assert terminate_regex(original_regex) == terminated_regex
 
 
 @pytest.mark.parametrize("schema_type", ["integer", "number"])
@@ -68,11 +87,34 @@ def test_generate_string_strategy_regex():
     assert re.fullmatch(schema["pattern"], regex_strategy.example())
 
 
+def test_generate_string_strategy_ascii():
+    schema = {"type": "string", "pattern": "^\\w{1,6}$"}
+    strategy = ResourceGenerator(schema).generate_schema_strategy(schema)
+    for _ in range(100):
+        assert re.match("^[A-Za-z0-9_]{1,6}$", strategy.example())
+
+
 def test_generate_string_strategy_format():
     schema = {"type": "string", "format": "arn"}
     strategy = ResourceGenerator(schema).generate_schema_strategy(schema)
     assert re.fullmatch(STRING_FORMATS["arn"], strategy.example())
 
+    schema = {"type": "string", "format": "date-time"}
+    strategy = ResourceGenerator(schema).generate_schema_strategy(schema)
+    assert re.match(STRING_FORMATS["date-time"], strategy.example())
+
+    schema = {"type": "string", "format": "time"}
+    strategy = ResourceGenerator(schema).generate_schema_strategy(schema)
+    assert re.match(STRING_FORMATS["time"], strategy.example())
+
+    schema = {"type": "string", "format": "date"}
+    strategy = ResourceGenerator(schema).generate_schema_strategy(schema)
+    assert re.match(STRING_FORMATS["date"], strategy.example())
+
+    schema = {"type": "string", "format": "email"}
+    strategy = ResourceGenerator(schema).generate_schema_strategy(schema)
+    assert re.match(STRING_FORMATS["email"], strategy.example())
+
 
 def test_generate_string_strategy_length():
     schema = {"type": "string", "minLength": 5, "maxLength": 10}