• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

localstack / localstack / 5f1fdd48-92f9-4508-ac88-80c0245b2bad

09 Apr 2025 07:07PM UTC coverage: 86.783% (-0.03%) from 86.809%
5f1fdd48-92f9-4508-ac88-80c0245b2bad

push

circleci

web-flow
EC2: generate security group ids using id manager concept (#12494)

Co-authored-by: Mathieu Cloutier <cloutier.mat0@gmail.com>

14 of 14 new or added lines in 1 file covered. (100.0%)

615 existing lines in 23 files now uncovered.

63754 of 73464 relevant lines covered (86.78%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.32
/localstack-core/localstack/utils/strings.py
1
import base64
1✔
2
import binascii
1✔
3
import hashlib
1✔
4
import itertools
1✔
5
import random
1✔
6
import re
1✔
7
import string
1✔
8
import uuid
1✔
9
import zlib
1✔
10
from typing import Dict, List, Union
1✔
11

12
from localstack.config import DEFAULT_ENCODING
1✔
13

14
_unprintables = (
1✔
15
    range(0x00, 0x09),
16
    range(0x0A, 0x0A),
17
    range(0x0B, 0x0D),
18
    range(0x0E, 0x20),
19
    range(0xD800, 0xE000),
20
    range(0xFFFE, 0x10000),
21
)
22

23
# regular expression for unprintable characters
24
# Based on https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
25
#     #x9 | #xA | #xD | #x20 to #xD7FF | #xE000 to #xFFFD | #x10000 to #x10FFFF
26
REGEX_UNPRINTABLE_CHARS = re.compile(
1✔
27
    f"[{re.escape(''.join(map(chr, itertools.chain(*_unprintables))))}]"
28
)
29

30

31
def to_str(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="strict") -> str:
1✔
32
    """If ``obj`` is an instance of ``binary_type``, return
33
    ``obj.decode(encoding, errors)``, otherwise return ``obj``"""
34
    return obj.decode(encoding, errors) if isinstance(obj, bytes) else obj
1✔
35

36

37
def to_bytes(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="strict") -> bytes:
1✔
38
    """If ``obj`` is an instance of ``text_type``, return
39
    ``obj.encode(encoding, errors)``, otherwise return ``obj``"""
40
    return obj.encode(encoding, errors) if isinstance(obj, str) else obj
1✔
41

42

43
def truncate(data: str, max_length: int = 100) -> str:
1✔
44
    data = str(data or "")
1✔
45
    return ("%s..." % data[:max_length]) if len(data) > max_length else data
1✔
46

47

48
def is_string(s, include_unicode=True, exclude_binary=False):
1✔
49
    if isinstance(s, bytes) and exclude_binary:
1✔
50
        return False
×
51
    if isinstance(s, str):
1✔
52
        return True
1✔
53
    if include_unicode and isinstance(s, str):
×
54
        return True
×
55
    return False
×
56

57

58
def is_string_or_bytes(s):
1✔
59
    return is_string(s) or isinstance(s, str) or isinstance(s, bytes)
×
60

61

62
def is_base64(s):
1✔
63
    regex = r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
1✔
64
    return is_string(s) and re.match(regex, s)
1✔
65

66

67
_re_camel_to_snake_case = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))")
1✔
68

69

70
def camel_to_snake_case(string: str) -> str:
1✔
71
    return _re_camel_to_snake_case.sub(r"_\1", string).replace("__", "_").lower()
1✔
72

73

74
def snake_to_camel_case(string: str, capitalize_first: bool = True) -> str:
1✔
75
    components = string.split("_")
1✔
76
    start_idx = 0 if capitalize_first else 1
1✔
77
    components = [x.title() for x in components[start_idx:]]
1✔
78
    return "".join(components)
1✔
79

80

81
def hyphen_to_snake_case(string: str) -> str:
1✔
UNCOV
82
    return string.replace("-", "_")
×
83

84

85
def canonicalize_bool_to_str(val: bool) -> str:
1✔
86
    return "true" if str(val).lower() == "true" else "false"
1✔
87

88

89
def convert_to_printable_chars(value: Union[List, Dict, str]) -> str:
1✔
90
    """Removes all unprintable characters from the given string."""
91
    from localstack.utils.objects import recurse_object
1✔
92

93
    if isinstance(value, (dict, list)):
1✔
94

95
        def _convert(obj, **kwargs):
1✔
96
            if isinstance(obj, str):
1✔
97
                return convert_to_printable_chars(obj)
1✔
98
            return obj
1✔
99

100
        return recurse_object(value, _convert)
1✔
101

102
    result = REGEX_UNPRINTABLE_CHARS.sub("", value)
1✔
103
    return result
1✔
104

105

106
def first_char_to_lower(s: str) -> str:
1✔
107
    return s and "%s%s" % (s[0].lower(), s[1:])
1✔
108

109

110
def first_char_to_upper(s: str) -> str:
1✔
111
    return s and "%s%s" % (s[0].upper(), s[1:])
1✔
112

113

114
def str_to_bool(value):
1✔
115
    """Return the boolean value of the given string, or the verbatim value if it is not a string"""
116
    if isinstance(value, str):
1✔
117
        true_strings = ["true", "True"]
1✔
118
        return value in true_strings
1✔
119
    return value
1✔
120

121

122
def str_insert(string, index, content):
1✔
123
    """Insert a substring into an existing string at a certain index."""
UNCOV
124
    return "%s%s%s" % (string[:index], content, string[index:])
×
125

126

127
def str_remove(string, index, end_index=None):
1✔
128
    """Remove a substring from an existing string at a certain from-to index range."""
UNCOV
129
    end_index = end_index or (index + 1)
×
130
    return "%s%s" % (string[:index], string[end_index:])
×
131

132

133
def str_startswith_ignore_case(value: str, prefix: str) -> bool:
1✔
UNCOV
134
    return value[: len(prefix)].lower() == prefix.lower()
×
135

136

137
def short_uid() -> str:
1✔
138
    return str(uuid.uuid4())[0:8]
1✔
139

140

141
def short_uid_from_seed(seed: str) -> str:
1✔
142
    hash = hashlib.sha1(seed.encode("utf-8")).hexdigest()
1✔
143
    truncated_hash = hash[:32]
1✔
144
    return str(uuid.UUID(truncated_hash))[0:8]
1✔
145

146

147
def long_uid() -> str:
1✔
148
    return str(uuid.uuid4())
1✔
149

150

151
def md5(string: Union[str, bytes]) -> str:
1✔
152
    m = hashlib.md5()
1✔
153
    m.update(to_bytes(string))
1✔
154
    return m.hexdigest()
1✔
155

156

157
def checksum_crc32(string: Union[str, bytes]) -> str:
1✔
158
    bytes = to_bytes(string)
1✔
159
    checksum = zlib.crc32(bytes)
1✔
160
    return base64.b64encode(checksum.to_bytes(4, "big")).decode()
1✔
161

162

163
def checksum_crc32c(string: Union[str, bytes]):
1✔
164
    # import botocore locally here to avoid a dependency of the CLI to botocore
165
    from botocore.httpchecksum import CrtCrc32cChecksum
1✔
166

167
    checksum = CrtCrc32cChecksum()
1✔
168
    checksum.update(to_bytes(string))
1✔
169
    return base64.b64encode(checksum.digest()).decode()
1✔
170

171

172
def checksum_crc64nvme(string: Union[str, bytes]):
1✔
173
    # import botocore locally here to avoid a dependency of the CLI to botocore
174
    from botocore.httpchecksum import CrtCrc64NvmeChecksum
1✔
175

176
    checksum = CrtCrc64NvmeChecksum()
1✔
177
    checksum.update(to_bytes(string))
1✔
178
    return base64.b64encode(checksum.digest()).decode()
1✔
179

180

181
def hash_sha1(string: Union[str, bytes]) -> str:
1✔
182
    digest = hashlib.sha1(to_bytes(string)).digest()
1✔
183
    return base64.b64encode(digest).decode()
1✔
184

185

186
def hash_sha256(string: Union[str, bytes]) -> str:
1✔
187
    digest = hashlib.sha256(to_bytes(string)).digest()
1✔
188
    return base64.b64encode(digest).decode()
1✔
189

190

191
def base64_to_hex(b64_string: str) -> bytes:
1✔
192
    return binascii.hexlify(base64.b64decode(b64_string))
1✔
193

194

195
def base64_decode(data: Union[str, bytes]) -> bytes:
1✔
196
    """Decode base64 data - with optional padding, and able to handle urlsafe encoding (containing -/_)."""
197
    data = to_str(data)
1✔
198
    missing_padding = len(data) % 4
1✔
199
    if missing_padding != 0:
1✔
UNCOV
200
        data = to_str(data) + "=" * (4 - missing_padding)
×
201
    if "-" in data or "_" in data:
1✔
202
        return base64.urlsafe_b64decode(data)
1✔
203
    return base64.b64decode(data)
1✔
204

205

206
def get_random_hex(length: int) -> str:
1✔
207
    return "".join(random.choices(string.hexdigits[:16], k=length)).lower()
1✔
208

209

210
def remove_leading_extra_slashes(input: str) -> str:
1✔
211
    """
212
    Remove leading extra slashes from the given input string.
213
    Example: '///foo/bar' -> '/foo/bar'
214
    """
UNCOV
215
    return re.sub(r"^/+", "/", input)
×
216

217

218
def prepend_with_slash(input: str) -> str:
1✔
219
    """
220
    Prepend a slash `/` to a given string if it does not have one already.
221
    """
222
    if not input.startswith("/"):
1✔
223
        return f"/{input}"
1✔
224
    return input
1✔
225

226

227
def key_value_pairs_to_dict(pairs: str, delimiter: str = ",", separator: str = "=") -> dict:
1✔
228
    """
229
    Converts a string of key-value pairs to a dictionary.
230

231
    Args:
232
        pairs (str): A string containing key-value pairs separated by a delimiter.
233
        delimiter (str): The delimiter used to separate key-value pairs (default is comma ',').
234
        separator (str): The separator between keys and values (default is '=').
235

236
    Returns:
237
        dict: A dictionary containing the parsed key-value pairs.
238
    """
239
    splits = [split_pair.partition(separator) for split_pair in pairs.split(delimiter)]
1✔
240
    return {key.strip(): value.strip() for key, _, value in splits}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc