Last active
November 18, 2022 13:48
-
-
Save marz619/674d36bb90f1cb91a72486741c6d692c to your computer and use it in GitHub Desktop.
Convert a 15 character Salesforce Identier into an 18 character Identifier
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# suffix characters (base 32, sort of) | |
SUFFIX_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345" | |
def id18(s: str, *, check_suffix: bool = False) -> str: | |
""" | |
Converts a 15 character Salesforce ID (s) to its 18 character version. | |
If the ID provided has length 18 and check_suffix is True, we will assert | |
that the suffix is correct! | |
>>> assert id18("a0BA0000000L2ZC") == "a0BA0000000L2ZCMA0" | |
>>> assert id18("AaaAABbbBBCccCC") == "AaaAABbbBBCccCCZZZ" | |
>>> assert id18("ABCDEFGHIJKLMNO") == "ABCDEFGHIJKLMNO555" | |
>>> assert id18("AAAAABBBBBCCCCC") == "AAAAABBBBBCCCCC555" | |
>>> assert id18("ABCDEABCDEABCDE") == "ABCDEABCDEABCDE555" | |
>>> assert id18("AAaAAbbBBBCcCCC") == "AAaAAbbBBBCcCCC123" | |
""" | |
if not isinstance(s, str): | |
raise TypeError("id must be type str") | |
if "" in (s, s.strip()): | |
raise ValueError("id must not be empty") | |
if len(s) not in (15, 18): | |
raise ValueError("id must have length 15, 18") | |
def suffix(p: str) -> str: | |
""" | |
Calculates the suffix for given a 15 character Salesforce ID | |
""" | |
return "".join([ | |
SUFFIX_CHARS[sum( | |
1 << i | |
for i, c in enumerate(p[n:n+5]) | |
if c.isupper() | |
)] for n in (0, 5, 10) | |
]) | |
if len(s) == 18: | |
if check_suffix: | |
exp, act = s[15:], suffix(s[:15]) | |
assert exp == act, f'id18: suffix mismatch for "{s}": "{exp}" != "{act}"' | |
return s | |
# length is 15; generate and return the id with the suffix | |
return f"{s}{suffix(s)}" | |
if __name__ == "__main__": | |
import doctest | |
doctest.testmod() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import List | |
def id18(s: str, *, check_suffix: bool = False) -> str: | |
""" | |
Converts a 15 character Salesforce ID (s) to its 18 character version. | |
If the ID provided has length 18 and check_suffix is True, we will assert | |
that the suffix is correct! | |
>>> assert id18("a0BA0000000L2ZC") == "a0BA0000000L2ZCMA0" | |
>>> assert id18("AaaAABbbBBCccCC") == "AaaAABbbBBCccCCZZZ" | |
>>> assert id18("ABCDEFGHIJKLMNO") == "ABCDEFGHIJKLMNO555" | |
>>> assert id18("AAAAABBBBBCCCCC") == "AAAAABBBBBCCCCC555" | |
>>> assert id18("ABCDEABCDEABCDE") == "ABCDEABCDEABCDE555" | |
>>> assert id18("AAaAAbbBBBCcCCC") == "AAaAAbbBBBCcCCC123" | |
>>> id18("") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in id18 | |
AssertionError: id18: id cannot be an empty string | |
>>> id18("AbCdEfGhIjKlMn") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in id18 | |
AssertionError: id18: id must have length 15 or 18 | |
>>> id18("AbCdEfGhIjKlMnOpQrS") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in id18 | |
AssertionError: id18: id must have length 15 or 18 | |
>>> id18("AAaAAbbBBBCcCCC ") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in id18 | |
AssertionError: id18: id cannot have trailing spaces | |
>>> id18(" AAaAAbbBBBCcCCC") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in id18 | |
AssertionError: id18: id cannot have trailing spaces | |
>>> id18(" AAaAAbbBBBCcCCC ") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in id18 | |
AssertionError: id18: id cannot have trailing spaces | |
""" | |
assert isinstance(s, str), "id18: id must be type str" | |
assert len(s), "id18: id cannot be an empty string" | |
assert s.strip() == s, "id18: id cannot have trailing spaces" | |
assert len(s) in (15, 18), "id18: id must have length 15 or 18" | |
if len(s) == 18: | |
if check_suffix: | |
exp, act = s[15:], suffix(s[:15]) | |
assert exp == act, f'id18: suffix mismatch for "{s}": "{exp}" != "{act}"' | |
return s | |
# length is 15; generate and return the ID with the suffix | |
return f"{s}{suffix(s)}" | |
def quintets(s: str) -> List[str]: | |
""" | |
>>> assert quintets("AAAAA") == ["AAAAA"] | |
>>> assert quintets("AAAAABBBBB") == ["AAAAA", "BBBBB"] | |
>>> assert quintets("AAAAABBBBBCCCCC") == ["AAAAA", "BBBBB", "CCCCC"] | |
>>> quintets("") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument cannot be an empty string | |
>>> quintets(" ") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument cannot have trailing spaces | |
>>> quintets(" ABCD") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument cannot have trailing spaces | |
>>> quintets("ABCD ") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument cannot have trailing spaces | |
>>> quintets(" ABCD ") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument cannot have trailing spaces | |
>>> quintets("ACBD") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument length must be divisible by 5 | |
>>> quintets("ACBDEABCD") | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in quintets | |
AssertionError: quintets: argument length must be divisible by 5 | |
""" | |
assert isinstance(s, str), "quintets: argument must be type str" | |
assert len(s), "quintets: argument cannot be an empty string" | |
assert s.strip() == s, "quintets: argument cannot have trailing spaces" | |
assert len(s) and len(s) % 5 == 0, "quintets: argument length must be divisible by 5" | |
# generate the list of quintets | |
return list(s[n:n+5] for n in range(0, len(s), 5)) | |
def suffix_char(i: int) -> str: | |
""" | |
>>> assert suffix_char(0) == "A" | |
>>> assert suffix_char(31) == "5" | |
>>> assert "".join(suffix_char(n) for n in range(0, 32)) == "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345" | |
>>> suffix_char(-1) | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in suffix_char | |
AssertionError: suffix_char: invalid index -1 | |
>>> suffix_char(32) | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in suffix_char | |
AssertionError: suffix_char: invalid index 32 | |
>>> suffix_char(None) | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "<stdin>", line 1, in suffix_char | |
AssertionError: suffix_char: index must be type int | |
""" | |
assert isinstance(i, int), "suffix_char: index must be type int" | |
assert 0 <= i <= 31, f"suffix_char: invalid index {i}" | |
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"[i] | |
def suffix_index(s: str) -> int: | |
assert isinstance(s, str), "suffix_index: argument must be type str" | |
assert len(s), "suffix_index: argument cannot be an empty str" | |
assert s.strip() == s, "suffix_index: argument cannot have trailing spaces" | |
assert len(s) == 5, "suffix_index: argument must have length 5" | |
idx = 0 | |
for i, c in enumerate(s): | |
if c.isupper(): | |
idx += 1 << i | |
return idx | |
def suffix(s: str) -> str: | |
""" | |
>>> assert suffix("a0BA0000000L2ZC") == "MA0" | |
>>> assert suffix("AaaAABbbBBCccCC") == "ZZZ" | |
>>> assert suffix("ABCDEFGHIJKLMNO") == "555" | |
>>> assert suffix("AAAAABBBBBCCCCC") == "555" | |
>>> assert suffix("ABCDEABCDEABCDE") == "555" | |
>>> assert suffix("AAaAAbbBBBCcCCC") == "123" | |
""" | |
assert isinstance(s, str), "suffix: argument must be type str" | |
assert s.strip() == s, "suffix: argument cannot have trailing spaces" | |
assert len(s) == 15, "suffix: argument must have length 15" | |
return "".join(suffix_char(suffix_index(q)) for q in quintets(s)) | |
if __name__ == "__main__": | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment