awspub 0.0.7__py3-none-any.whl → 0.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
awspub/configmodels.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import pathlib
2
+ from enum import Enum
2
3
  from typing import Dict, List, Literal, Optional
3
4
 
4
5
  from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -94,6 +95,34 @@ class ConfigImageSSMParameterModel(BaseModel):
94
95
  )
95
96
 
96
97
 
98
+ class SNSNotificationProtocol(str, Enum):
99
+ DEFAULT = "default"
100
+ EMAIL = "email"
101
+
102
+
103
+ class ConfigImageSNSNotificationModel(BaseModel):
104
+ """
105
+ Image/AMI SNS Notification specific configuration to notify subscribers about new images availability
106
+ """
107
+
108
+ model_config = ConfigDict(extra="forbid")
109
+
110
+ subject: str = Field(description="The subject of SNS Notification", min_length=1, max_length=99)
111
+ message: Dict[SNSNotificationProtocol, str] = Field(
112
+ description="The body of the message to be sent to subscribers.",
113
+ default={SNSNotificationProtocol.DEFAULT: ""},
114
+ )
115
+
116
+ @field_validator("message")
117
+ def check_message(cls, value):
118
+ # Check message protocols have default key
119
+ # Message should contain at least a top-level JSON key of “default”
120
+ # with a value that is a string
121
+ if SNSNotificationProtocol.DEFAULT not in value:
122
+ raise ValueError(f"{SNSNotificationProtocol.DEFAULT.value} key is required to send SNS notification")
123
+ return value
124
+
125
+
97
126
  class ConfigImageModel(BaseModel):
98
127
  """
99
128
  Image/AMI configuration.
@@ -148,6 +177,9 @@ class ConfigImageModel(BaseModel):
148
177
  )
149
178
  groups: Optional[List[str]] = Field(description="Optional list of groups this image is part of", default=[])
150
179
  tags: Optional[Dict[str, str]] = Field(description="Optional Tags to apply to this image only", default={})
180
+ sns: Optional[List[Dict[str, ConfigImageSNSNotificationModel]]] = Field(
181
+ description="Optional list of SNS Notification related configuration", default=None
182
+ )
151
183
 
152
184
  @field_validator("share")
153
185
  @classmethod
awspub/exceptions.py CHANGED
@@ -14,3 +14,11 @@ class BucketDoesNotExistException(Exception):
14
14
  def __init__(self, bucket_name: str, *args, **kwargs):
15
15
  msg = f"The bucket named '{bucket_name}' does not exist. You will need to create the bucket before proceeding."
16
16
  super().__init__(msg, *args, **kwargs)
17
+
18
+
19
+ class AWSNotificationException(Exception):
20
+ pass
21
+
22
+
23
+ class AWSAuthorizationException(Exception):
24
+ pass
awspub/image.py CHANGED
@@ -14,6 +14,7 @@ from awspub.context import Context
14
14
  from awspub.image_marketplace import ImageMarketplace
15
15
  from awspub.s3 import S3
16
16
  from awspub.snapshot import Snapshot
17
+ from awspub.sns import SNSNotification
17
18
 
18
19
  logger = logging.getLogger(__name__)
19
20
 
@@ -353,6 +354,19 @@ class Image:
353
354
  else:
354
355
  logger.error(f"image {self.image_name} not available in region {region}. can not make public")
355
356
 
357
+ def _sns_publish(self) -> None:
358
+ """
359
+ Publish SNS notifiations about newly available images to subscribers
360
+ """
361
+ for region in self.image_regions:
362
+ ec2client_region: EC2Client = boto3.client("ec2", region_name=region)
363
+ image_info: Optional[_ImageInfo] = self._get(ec2client_region)
364
+
365
+ if not image_info:
366
+ logger.error(f"can not send SNS notification for {self.image_name} because no image found in {region}")
367
+ return
368
+ SNSNotification(self._ctx, self.image_name, region).publish()
369
+
356
370
  def cleanup(self) -> None:
357
371
  """
358
372
  Cleanup/delete the temporary images
@@ -556,6 +570,10 @@ class Image:
556
570
  f"currently using partition {partition}. Ignoring marketplace config."
557
571
  )
558
572
 
573
+ # send ssn notification
574
+ if self.conf["sns"]:
575
+ self._sns_publish()
576
+
559
577
  def _verify(self, region: str) -> List[ImageVerificationErrors]:
560
578
  """
561
579
  Verify (but don't modify or create anything) the image in a single region
awspub/sns.py ADDED
@@ -0,0 +1,88 @@
1
+ """
2
+ Methods used to handle notifications for AWS using SNS
3
+ """
4
+
5
+ import json
6
+ import logging
7
+ from typing import Any, Dict, List
8
+
9
+ import boto3
10
+ from botocore.exceptions import ClientError
11
+ from mypy_boto3_sns.client import SNSClient
12
+ from mypy_boto3_sts.client import STSClient
13
+
14
+ from awspub.context import Context
15
+ from awspub.exceptions import AWSAuthorizationException, AWSNotificationException
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class SNSNotification(object):
21
+ """
22
+ A data object that contains validation logic and
23
+ structuring rules for SNS notification JSON
24
+ """
25
+
26
+ def __init__(self, context: Context, image_name: str, region_name: str):
27
+ """
28
+ Construct a message and verify that it is valid
29
+ """
30
+ self._ctx: Context = context
31
+ self._image_name: str = image_name
32
+ self._region_name: str = region_name
33
+
34
+ @property
35
+ def conf(self) -> List[Dict[str, Any]]:
36
+ """
37
+ The sns configuration for the current image (based on "image_name") from context
38
+ """
39
+ return self._ctx.conf["images"][self._image_name]["sns"]
40
+
41
+ def _get_topic_arn(self, topic_name: str) -> str:
42
+ """
43
+ Calculate topic ARN based on partition, region, account and topic name
44
+ :param topic_name: Name of topic
45
+ :type topic_name: str
46
+ :param region_name: name of region
47
+ :type region_name: str
48
+ :return: return topic ARN
49
+ :rtype: str
50
+ """
51
+
52
+ stsclient: STSClient = boto3.client("sts", region_name=self._region_name)
53
+ resp = stsclient.get_caller_identity()
54
+
55
+ account = resp["Account"]
56
+ # resp["Arn"] has string format "arn:partition:iam::accountnumber:user/iam_role"
57
+ partition = resp["Arn"].rsplit(":")[1]
58
+
59
+ return f"arn:{partition}:sns:{self._region_name}:{account}:{topic_name}"
60
+
61
+ def publish(self) -> None:
62
+ """
63
+ send notification to subscribers
64
+ """
65
+
66
+ snsclient: SNSClient = boto3.client("sns", region_name=self._region_name)
67
+
68
+ for topic in self.conf:
69
+ for topic_name, topic_config in topic.items():
70
+ try:
71
+ snsclient.publish(
72
+ TopicArn=self._get_topic_arn(topic_name),
73
+ Subject=topic_config["subject"],
74
+ Message=json.dumps(topic_config["message"]),
75
+ MessageStructure="json",
76
+ )
77
+ except ClientError as e:
78
+ exception_code: str = e.response["Error"]["Code"]
79
+ if exception_code == "AuthorizationError":
80
+ raise AWSAuthorizationException(
81
+ "Profile does not have a permission to send the SNS notification. Please review the policy."
82
+ )
83
+ else:
84
+ raise AWSNotificationException(str(e))
85
+ logger.info(
86
+ f"The SNS notification {topic_config['subject']}"
87
+ f" for the topic {topic_name} in {self._region_name} has been sent."
88
+ )
@@ -118,6 +118,35 @@ awspub:
118
118
  -
119
119
  name: /awspub-test/param2
120
120
  allow_overwrite: true
121
+ "test-image-10":
122
+ boot_mode: "uefi"
123
+ description: |
124
+ A test image without a separate snapshot but single sns configs
125
+ regions:
126
+ - "us-east-1"
127
+ sns:
128
+ - "topic1":
129
+ subject: "topic1-subject"
130
+ message:
131
+ default: "default-message"
132
+ email: "email-message"
133
+ "test-image-11":
134
+ boot_mode: "uefi"
135
+ description: |
136
+ A test image without a separate snapshot but multiple sns configs
137
+ regions:
138
+ - "us-east-1"
139
+ - "eu-central-1"
140
+ sns:
141
+ - "topic1":
142
+ subject: "topic1-subject"
143
+ message:
144
+ default: "default-message"
145
+ email: "email-message"
146
+ - "topic2":
147
+ subject: "topic2-subject"
148
+ message:
149
+ default: "default-message"
121
150
 
122
151
  tags:
123
152
  name: "foobar"
awspub/tests/test_api.py CHANGED
@@ -23,6 +23,8 @@ curdir = pathlib.Path(__file__).parent.resolve()
23
23
  "test-image-7",
24
24
  "test-image-8",
25
25
  "test-image-9",
26
+ "test-image-10",
27
+ "test-image-11",
26
28
  ],
27
29
  ),
28
30
  # with a group that no image as, no image should be processed
@@ -127,16 +127,32 @@ def test_image___get_root_device_snapshot_id(root_device_name, block_device_mapp
127
127
 
128
128
 
129
129
  @pytest.mark.parametrize(
130
- "imagename,partition,called_mod_image,called_mod_snapshot,called_start_change_set,called_put_parameter",
130
+ (
131
+ "imagename",
132
+ "partition",
133
+ "called_mod_image",
134
+ "called_mod_snapshot",
135
+ "called_start_change_set",
136
+ "called_put_parameter",
137
+ "called_sns_publish",
138
+ ),
131
139
  [
132
- ("test-image-6", "aws", True, True, False, False),
133
- ("test-image-7", "aws", False, False, False, False),
134
- ("test-image-8", "aws", True, True, True, True),
135
- ("test-image-8", "aws-cn", True, True, False, True),
140
+ ("test-image-6", "aws", True, True, False, False, False),
141
+ ("test-image-7", "aws", False, False, False, False, False),
142
+ ("test-image-8", "aws", True, True, True, True, False),
143
+ ("test-image-8", "aws-cn", True, True, False, True, False),
144
+ ("test-image-10", "aws", False, False, False, False, True),
145
+ ("test-image-11", "aws", False, False, False, False, True),
136
146
  ],
137
147
  )
138
148
  def test_image_publish(
139
- imagename, partition, called_mod_image, called_mod_snapshot, called_start_change_set, called_put_parameter
149
+ imagename,
150
+ partition,
151
+ called_mod_image,
152
+ called_mod_snapshot,
153
+ called_start_change_set,
154
+ called_put_parameter,
155
+ called_sns_publish,
140
156
  ):
141
157
  """
142
158
  Test the publish() for a given image
@@ -167,6 +183,7 @@ def test_image_publish(
167
183
  "Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
168
184
  }
169
185
  instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
186
+ instance.list_topics.return_value = {"Topics": [{"TopicArn": "arn:aws:sns:topic1"}]}
170
187
  ctx = context.Context(curdir / "fixtures/config1.yaml", None)
171
188
  img = image.Image(ctx, imagename)
172
189
  img.publish()
@@ -174,6 +191,7 @@ def test_image_publish(
174
191
  assert instance.modify_snapshot_attribute.called == called_mod_snapshot
175
192
  assert instance.start_change_set.called == called_start_change_set
176
193
  assert instance.put_parameter.called == called_put_parameter
194
+ assert instance.publish.called == called_sns_publish
177
195
 
178
196
 
179
197
  def test_image__get_zero_images():
@@ -0,0 +1,140 @@
1
+ import pathlib
2
+ from unittest.mock import patch
3
+
4
+ import botocore.exceptions
5
+ import pytest
6
+
7
+ from awspub import context, exceptions, sns
8
+
9
+ curdir = pathlib.Path(__file__).parent.resolve()
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ "imagename,called_sns_publish, publish_call_count",
14
+ [
15
+ ("test-image-10", True, 1),
16
+ ("test-image-11", True, 4),
17
+ ],
18
+ )
19
+ def test_sns_publish(imagename, called_sns_publish, publish_call_count):
20
+ """
21
+ Test the send_notification logic
22
+ """
23
+ with patch("boto3.client") as bclient_mock:
24
+ instance = bclient_mock.return_value
25
+ ctx = context.Context(curdir / "fixtures/config1.yaml", None)
26
+ image_conf = ctx.conf["images"][imagename]
27
+
28
+ for region in image_conf["regions"]:
29
+ sns.SNSNotification(ctx, imagename, region).publish()
30
+
31
+ assert instance.publish.called == called_sns_publish
32
+ assert instance.publish.call_count == publish_call_count
33
+
34
+
35
+ @pytest.mark.parametrize(
36
+ "imagename",
37
+ [
38
+ ("test-image-10"),
39
+ ("test-image-11"),
40
+ ],
41
+ )
42
+ def test_sns_publish_fail_with_invalid_topic(imagename):
43
+ """
44
+ Test the send_notification logic
45
+ """
46
+ with patch("boto3.client") as bclient_mock:
47
+ instance = bclient_mock.return_value
48
+ ctx = context.Context(curdir / "fixtures/config1.yaml", None)
49
+ image_conf = ctx.conf["images"][imagename]
50
+
51
+ # topic1 is invalid topic
52
+ def side_effect(*args, **kwargs):
53
+ topic_arn = kwargs.get("TopicArn")
54
+ if "topic1" in topic_arn:
55
+ error_reponse = {
56
+ "Error": {
57
+ "Code": "NotFoundException",
58
+ "Message": "An error occurred (NotFound) when calling the Publish operation: "
59
+ "Topic does not exist.",
60
+ }
61
+ }
62
+ raise botocore.exceptions.ClientError(error_reponse, "")
63
+
64
+ instance.publish.side_effect = side_effect
65
+
66
+ for region in image_conf["regions"]:
67
+ with pytest.raises(exceptions.AWSNotificationException):
68
+ sns.SNSNotification(ctx, imagename, region).publish()
69
+
70
+
71
+ @pytest.mark.parametrize(
72
+ "imagename",
73
+ [
74
+ ("test-image-10"),
75
+ ("test-image-11"),
76
+ ],
77
+ )
78
+ def test_sns_publish_fail_with_unauthorized_user(imagename):
79
+ """
80
+ Test the send_notification logic
81
+ """
82
+ with patch("boto3.client") as bclient_mock:
83
+ instance = bclient_mock.return_value
84
+ ctx = context.Context(curdir / "fixtures/config1.yaml", None)
85
+ image_conf = ctx.conf["images"][imagename]
86
+
87
+ error_reponse = {
88
+ "Error": {
89
+ "Code": "AuthorizationError",
90
+ "Message": "User are not authorized perform SNS Notification service",
91
+ }
92
+ }
93
+ instance.publish.side_effect = botocore.exceptions.ClientError(error_reponse, "")
94
+
95
+ for region in image_conf["regions"]:
96
+ with pytest.raises(exceptions.AWSAuthorizationException):
97
+ sns.SNSNotification(ctx, imagename, region).publish()
98
+
99
+
100
+ @pytest.mark.parametrize(
101
+ "imagename, partition, expected",
102
+ [
103
+ (
104
+ "test-image-10",
105
+ "aws-cn",
106
+ [
107
+ "arn:aws-cn:sns:us-east-1:1234:topic1",
108
+ ],
109
+ ),
110
+ (
111
+ "test-image-11",
112
+ "aws",
113
+ [
114
+ "arn:aws:sns:us-east-1:1234:topic1",
115
+ "arn:aws:sns:us-east-1:1234:topic2",
116
+ "arn:aws:sns:eu-central-1:1234:topic1",
117
+ "arn:aws:sns:eu-central-1:1234:topic2",
118
+ ],
119
+ ),
120
+ ],
121
+ )
122
+ def test_sns__get_topic_arn(imagename, partition, expected):
123
+ """
124
+ Test the send_notification logic
125
+ """
126
+ with patch("boto3.client") as bclient_mock:
127
+ instance = bclient_mock.return_value
128
+ ctx = context.Context(curdir / "fixtures/config1.yaml", None)
129
+ image_conf = ctx.conf["images"][imagename]
130
+
131
+ instance.get_caller_identity.return_value = {"Account": "1234", "Arn": f"arn:{partition}:iam::1234:user/test"}
132
+
133
+ topic_arns = []
134
+ for region in image_conf["regions"]:
135
+ for topic in image_conf["sns"]:
136
+ topic_name = next(iter(topic))
137
+ res_arn = sns.SNSNotification(ctx, imagename, region)._get_topic_arn(topic_name)
138
+ topic_arns.append(res_arn)
139
+
140
+ assert topic_arns == expected
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: awspub
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Publish images to AWS EC2
5
5
  Home-page: https://github.com/canonical/awspub
6
6
  License: GPL-3.0-or-later
@@ -14,9 +14,10 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
17
18
  Requires-Dist: autodoc-pydantic (>=2.0.1,<3.0.0)
18
19
  Requires-Dist: boto3
19
- Requires-Dist: boto3-stubs[essential,marketplace-catalog,s3,ssm] (>=1.28.83,<2.0.0)
20
+ Requires-Dist: boto3-stubs[essential,marketplace-catalog,s3,sns,ssm,sts] (>=1.28.83,<2.0.0)
20
21
  Requires-Dist: pydantic (>=2,<3)
21
22
  Requires-Dist: ruamel-yaml (>=0.18.6,<0.19.0)
22
23
  Project-URL: Repository, https://github.com/canonical/awspub
@@ -44,20 +45,3 @@ License
44
45
 
45
46
  The project uses `GPL-3.0` as license.
46
47
 
47
- Doing a new release
48
- ===================
49
-
50
- New releases are mostly automated.
51
-
52
- pypi
53
- ----
54
-
55
- For a new release on pypi, create a new tag (following semantic versioning)
56
- with a `v` as prefix (eg. `v0.2.1`).
57
-
58
- snapstore
59
- ---------
60
-
61
- The latest git commit will be automatically build and published to the `latest/edge`
62
- channel. Manually promote from `latest/edge` to `latest/stable`.
63
-
@@ -2,32 +2,34 @@ awspub/__init__.py,sha256=7hgLrq6k53yaJrjFe7X5Cm45z3SIc1Vxocb5k3G8xPc,124
2
2
  awspub/api.py,sha256=d1gx9LdqdYXRLf8yZ_spIz_93WhB2GNnCG_x3ABrMkI,6497
3
3
  awspub/cli/__init__.py,sha256=-zCBEbnt5zbvSZ8PxQALpPAy0CiQUf-qZnikJ7U4Sf0,5621
4
4
  awspub/common.py,sha256=M_Ibw8DoAHG3oLoK5qRUggEjI7kJSSslC7r9VySe8vk,562
5
- awspub/configmodels.py,sha256=DMrC3N8V_zj2SuBRJu27dxxY4Um5rcemWFFqMlA6j9E,7824
5
+ awspub/configmodels.py,sha256=eHYS09ZA0tIYUWf2upD-Ymy-KMjfVanBl6UHRvDvjpA,9055
6
6
  awspub/context.py,sha256=LDkp9Sz5AqRxQq70ICgFIJn5g2qrc5qiVawTyS_rXZE,4064
7
- awspub/exceptions.py,sha256=SbGf9XyiGlj6estlraAwWAKLtuEfzwEuAbHXYiCiJD0,447
8
- awspub/image.py,sha256=9ms0sEiDmjrZ2rWpgS10-zCD25yw49dh5r7NDv0-Q7o,27589
7
+ awspub/exceptions.py,sha256=2JUEPhZ3sir2NoAuqteFYlh84LsrD7vaeIpkiWtiBpc,556
8
+ awspub/image.py,sha256=G3vRj0TF-WsTn0Jr4Q2xmS1sTLfntUG1jydbG3NwAEM,28308
9
9
  awspub/image_marketplace.py,sha256=oiD7yNU5quG5CQG9Ql5Ut9hLWA1yewg6qVwTbyadGwc,5314
10
10
  awspub/s3.py,sha256=ivR8DuAkYilph73EjFkTgUelkXxU7pZfosnsHHyoZkQ,11274
11
11
  awspub/snapshot.py,sha256=V5e_07SnmCwEPjRmwZh43spWparhH8X4ugG16uQfGuo,10040
12
+ awspub/sns.py,sha256=dOO5_Rs1ZUeDiBCNVyh4U6oo-Qht-jaAENOg3g5WJpA,3106
12
13
  awspub/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  awspub/tests/fixtures/config-invalid-s3-extra.yaml,sha256=TdgqE-quxgueXS9L8ixsRuG6eTVfqalZ41G3JNCWn58,288
14
15
  awspub/tests/fixtures/config-minimal.yaml,sha256=oHupXHYQXxmqgN2qFCAwvxzR7Bch-3yScrmMXeMIICE,176
15
16
  awspub/tests/fixtures/config-valid-nonawspub.yaml,sha256=Md-YINQQRo3kveikUxk8Co9BYIZfDftmPT2LmIqoTL4,330
16
17
  awspub/tests/fixtures/config1.vmdk,sha256=YlJHVAi5-e5kRSthHXBqB4gxqZsSPbadFE2HigSIoKg,65536
17
- awspub/tests/fixtures/config1.yaml,sha256=QUxX7j7SNP40CeSIRFcXm54Ef2CmzgDeWfYYy_THlM0,3256
18
+ awspub/tests/fixtures/config1.yaml,sha256=TjGAhzgvDiVu1ILAIKPg_ka0TozYGDoZTrhNUO2LCYw,4089
18
19
  awspub/tests/fixtures/config2-mapping.yaml,sha256=lqJE0ej9DdGsE8O5dqG5PX7bOJrY4nMciXoOzMzV-so,31
19
20
  awspub/tests/fixtures/config2.yaml,sha256=m2v-n1T-XPGDHyrJXArC_rYV-ZPMr9cgzHkLXiSRuDs,1250
20
21
  awspub/tests/fixtures/config3-duplicate-keys.yaml,sha256=Cn0tTQawpEFocDNpWxDz1651uQa7aw88XjNyPcCG4iQ,324
21
- awspub/tests/test_api.py,sha256=7MKm2aCtcvHJ0x_o2qinljfL9xFBWnasUnVpBxB37w8,2504
22
+ awspub/tests/test_api.py,sha256=vdMUwICUoDwuDd-HuATUttHv6Up1xX5PZebAfdQEK-w,2570
22
23
  awspub/tests/test_cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
24
  awspub/tests/test_common.py,sha256=kytMUU47uZYYe302XswdO15qX_i1vO2LS5n96--TcSU,478
24
25
  awspub/tests/test_context.py,sha256=wMXQqj4vi2U3q5w1xPV-stB3mp3K6puUyXhsShJG4wA,3115
25
- awspub/tests/test_image.py,sha256=tMcMx8rnx0q3oeRBA3JSeOHVxnnUGG_AAHVsZ7DWNYw,19083
26
+ awspub/tests/test_image.py,sha256=T7vDFHRSaqTG-jthJLDiKcv791Q4LMM4Crh-6U1ImjM,19548
26
27
  awspub/tests/test_image_marketplace.py,sha256=JP7PrFjix1AyQg7eEaQ-wCROVoIOb873koseniOqGQQ,1456
27
28
  awspub/tests/test_s3.py,sha256=UJL8CQDEvhA42MwPGeSvSbQFj8h86c1LrLFDvcMcRws,2857
28
29
  awspub/tests/test_snapshot.py,sha256=8KPTqGVyzrpivWuq3HE7ZhgtLllcr3rA_3hZcxu2xjg,4123
29
- awspub-0.0.7.dist-info/LICENSE,sha256=9GbrzFQ3rWjVKj-IZnX1kGDsIGIdjc25KGRmAp03Jn0,35150
30
- awspub-0.0.7.dist-info/METADATA,sha256=AHGXMR-iFQqINhVpdGyvFTkFChcYfzqdHf5s-tEjmi8,1773
31
- awspub-0.0.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
32
- awspub-0.0.7.dist-info/entry_points.txt,sha256=hrQzy9P5yO58nj6W0UDPdQPUTqEkQLpMvuyDDRu7LRQ,42
33
- awspub-0.0.7.dist-info/RECORD,,
30
+ awspub/tests/test_sns.py,sha256=vm99g_PRZiwc8b87wcnR86lVX4TnthYvtvjlZ4QbwVk,4421
31
+ awspub-0.0.8.dist-info/LICENSE,sha256=9GbrzFQ3rWjVKj-IZnX1kGDsIGIdjc25KGRmAp03Jn0,35150
32
+ awspub-0.0.8.dist-info/METADATA,sha256=WsTaPubZWaJ86ZNx1syaj5F9sgC0Kc4VZeRzWMh420k,1458
33
+ awspub-0.0.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
34
+ awspub-0.0.8.dist-info/entry_points.txt,sha256=hrQzy9P5yO58nj6W0UDPdQPUTqEkQLpMvuyDDRu7LRQ,42
35
+ awspub-0.0.8.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any