diff --git a/awsimple/__version__.py b/awsimple/__version__.py index 0a695ee..ccb4dd0 100644 --- a/awsimple/__version__.py +++ b/awsimple/__version__.py @@ -1,7 +1,7 @@ __application_name__ = "awsimple" __title__ = __application_name__ __author__ = "abel" -__version__ = "4.2.0" +__version__ = "5.0.0" __author_email__ = "j@abel.co" __url__ = "https://github.com/jamesabel/awsimple" __download_url__ = "https://github.com/jamesabel/awsimple" diff --git a/awsimple/pubsub.py b/awsimple/pubsub.py index 6093a75..b1d6256 100644 --- a/awsimple/pubsub.py +++ b/awsimple/pubsub.py @@ -14,6 +14,7 @@ from typeguard import typechecked from botocore.exceptions import ClientError +import strif from .sns import SNSAccess from .sqs import SQSPollAccess, get_all_sqs_queues @@ -121,17 +122,17 @@ def run(self): @lru_cache -def make_name_aws_safe(name: str) -> str: +def make_name_aws_safe(*args: str) -> str: """ - Make a name safe for an SQS queue to subscribe to an SNS topic. + Make a name safe for an SQS queue to subscribe to an SNS topic. AWS has a bunch of undocumented restrictions on names, so we just hash the name to a base36 string. - :param name: input name + :params: input name(s) :return: AWS safe name """ - safe_name = "".join([c for c in name.strip().lower() if c.isalnum()]) # only allow alphanumeric characters - if len(safe_name) < 1: - raise ValueError(f'"{name}" is not valid after making AWS safe - result must contain at least one alphanumeric character.') - return safe_name + + base36 = strif.hash_string("".join(args)).base36 + assert len(base36) == 31 + return base36 class PubSub(Process): @@ -155,9 +156,10 @@ def __init__( :param node_name: Node name (SQS queue name suffix). Defaults to a combination of computer name and username, but can be passed in for customization and/or testing. :param sub_callback: Optional thread and process safe callback function to be called when a new message is received. The function should accept a single argument, which will be the message as a dictionary. """ - - self.channel = "ps" + make_name_aws_safe(channel) # prefix with ps (pubsub) to avoid collisions with other uses of SNS topics and SQS queues - self.node_name = make_name_aws_safe(node_name) + self.aws_resource_prefix = "ps" # for pubsub + self.channel = self.aws_resource_prefix + make_name_aws_safe(channel) # prefix with ps (pubsub) to avoid collisions with other uses of SNS topics and SQS queues + self.node_name = node_name + self.sqs_queue_name = self.aws_resource_prefix + make_name_aws_safe(self.channel, self.node_name) self.sub_callback = sub_callback self.profile_name = profile_name @@ -174,8 +176,6 @@ def __init__( def run(self): - sqs_queue_name = f"{self.channel}{self.node_name}" - sns = SNSAccess( self.channel, auto_create=True, @@ -185,11 +185,16 @@ def run(self): region_name=self.region_name, ) sqs_metadata = _DynamoDBMetadataTable( - sqs_name, sqs_queue_name, profile_name=self.profile_name, aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.region_name + sqs_name, + self.sqs_queue_name, + profile_name=self.profile_name, + aws_access_key_id=self.aws_access_key_id, + aws_secret_access_key=self.aws_secret_access_key, + region_name=self.region_name, ) sqs = SQSPollAccess( - sqs_queue_name, profile_name=self.profile_name, aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.region_name + self.sqs_queue_name, profile_name=self.profile_name, aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.region_name ) if not sqs.exists(): sqs.create_queue() diff --git a/requirements-dev.txt b/requirements-dev.txt index fe1bbdc..af02155 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,6 +9,7 @@ tobool urllib3 python-dateutil yasf +strif # # examples ismain diff --git a/setup.py b/setup.py index 85c5b3a..c880807 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ keywords=["aws", "cloud", "storage", "database", "dynamodb", "s3"], packages=[__title__], package_data={__title__: [readme_file_path, "py.typed"]}, - install_requires=["boto3", "typeguard", "hashy>=0.1.1", "dictim", "appdirs", "tobool", "urllib3", "python-dateutil", "yasf"], + install_requires=["boto3", "typeguard", "hashy>=0.1.1", "dictim", "appdirs", "tobool", "urllib3", "python-dateutil", "yasf", "strif"], project_urls={"Documentation": "https://awsimple.readthedocs.io/"}, classifiers=[], python_requires=">3.10", diff --git a/test_awsimple/test_pubsub/test_pubsub_make_name_aws_safe.py b/test_awsimple/test_pubsub/test_pubsub_make_name_aws_safe.py new file mode 100644 index 0000000..6104f8f --- /dev/null +++ b/test_awsimple/test_pubsub/test_pubsub_make_name_aws_safe.py @@ -0,0 +1,16 @@ +from awsimple.pubsub import make_name_aws_safe + + +def test_pubsub_make_name_aws_safe(): + + assert make_name_aws_safe("My Topic Name!") == "lc6dwk3bn32n6upqpos4ryluxluxsvd" + assert make_name_aws_safe("Topic@123") == "jwxskzojw9o2717rs24rtwqgy50v4yn" + assert make_name_aws_safe("with.a.dot") == "boo0z8dyxiirijsg69qg4g2yg3tsd7w" + assert make_name_aws_safe("a_6.3") == "d4gg6yrpd2pieqhchpz2qlc7a52shxy" + assert make_name_aws_safe("-5") == "dk1zux1ndufnok5x2v4uj3flwtzxpr6" + assert make_name_aws_safe("0") == "lasw0dw8dpjcoalne79l4km57y91kwc" + assert make_name_aws_safe("Valid_Name-123") == "5fpq80the6qly6weat39tnh57x8bjhm" + assert make_name_aws_safe("Invalid#Name$With%Special&Chars*") == "jbudqy4oq2aqhcxgs40b0nmahou3pgq" + + assert make_name_aws_safe("ab") == "phbce4exwcst2t3d0hqd8k81nc27kd8" + assert make_name_aws_safe("a", "b") == "phbce4exwcst2t3d0hqd8k81nc27kd8" diff --git a/test_awsimple/test_pubsub_make_name_aws_safe.py b/test_awsimple/test_pubsub_make_name_aws_safe.py deleted file mode 100644 index ca3170d..0000000 --- a/test_awsimple/test_pubsub_make_name_aws_safe.py +++ /dev/null @@ -1,23 +0,0 @@ -import pytest - -from awsimple.pubsub import make_name_aws_safe - - -def test_pubsub_make_name_aws_safe(): - - assert make_name_aws_safe("My Topic Name!") == "mytopicname" - assert make_name_aws_safe("Topic@123") == "topic123" - assert make_name_aws_safe("with.a.dot") == "withadot" - assert make_name_aws_safe("a_6.3") == "a63" - assert make_name_aws_safe("-5") == "5" - assert make_name_aws_safe("0") == "0" - assert make_name_aws_safe("Valid_Name-123") == "validname123" - assert make_name_aws_safe("Invalid#Name$With%Special&Chars*") == "invalidnamewithspecialchars" - - -def test_pubsub_make_name_aws_safe_empty(): - with pytest.raises(ValueError): - assert make_name_aws_safe("!!!") == "" - - with pytest.raises(ValueError): - assert make_name_aws_safe(".") == ""