finalsa-common-models 1.0.1__tar.gz → 2.0.1__tar.gz
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.
- finalsa_common_models-2.0.1/.gitignore +115 -0
- {finalsa_common_models-1.0.1 → finalsa_common_models-2.0.1}/LICENSE.md +1 -1
- finalsa_common_models-2.0.1/PKG-INFO +87 -0
- finalsa_common_models-2.0.1/README.md +52 -0
- finalsa_common_models-2.0.1/finalsa/common/models/__init__.py +18 -0
- finalsa_common_models-2.0.1/finalsa/common/models/models/__init__.py +12 -0
- finalsa_common_models-2.0.1/finalsa/common/models/models/functions.py +79 -0
- finalsa_common_models-2.0.1/finalsa/common/models/models/meta.py +10 -0
- finalsa_common_models-2.0.1/finalsa/common/models/models/sqs_response.py +64 -0
- finalsa_common_models-2.0.1/pyproject.toml +45 -0
- finalsa_common_models-1.0.1/PKG-INFO +0 -23
- finalsa_common_models-1.0.1/README.md +0 -0
- finalsa_common_models-1.0.1/finalsa/common/models/__init__.py +0 -15
- finalsa_common_models-1.0.1/finalsa/common/models/models/__init__.py +0 -3
- finalsa_common_models-1.0.1/finalsa/common/models/models/functions.py +0 -42
- finalsa_common_models-1.0.1/finalsa/common/models/models/sqs_message.py +0 -27
- finalsa_common_models-1.0.1/finalsa/common/models/models/sqs_response.py +0 -114
- finalsa_common_models-1.0.1/finalsa/common/models/py.typed +0 -0
- finalsa_common_models-1.0.1/finalsa_common_models.egg-info/PKG-INFO +0 -23
- finalsa_common_models-1.0.1/finalsa_common_models.egg-info/SOURCES.txt +0 -17
- finalsa_common_models-1.0.1/finalsa_common_models.egg-info/dependency_links.txt +0 -1
- finalsa_common_models-1.0.1/finalsa_common_models.egg-info/requires.txt +0 -2
- finalsa_common_models-1.0.1/finalsa_common_models.egg-info/top_level.txt +0 -2
- finalsa_common_models-1.0.1/finalsa_common_models.egg-info/zip-safe +0 -1
- finalsa_common_models-1.0.1/setup.cfg +0 -10
- finalsa_common_models-1.0.1/setup.py +0 -77
- finalsa_common_models-1.0.1/tests/test_client.py +0 -355
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
MANIFEST
|
|
27
|
+
|
|
28
|
+
# PyInstaller
|
|
29
|
+
# Usually these files are written by a python script from a template
|
|
30
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
|
|
51
|
+
# Translations
|
|
52
|
+
*.mo
|
|
53
|
+
*.pot
|
|
54
|
+
|
|
55
|
+
# Django stuff:
|
|
56
|
+
*.log
|
|
57
|
+
local_settings.py
|
|
58
|
+
db.sqlite3
|
|
59
|
+
|
|
60
|
+
# Flask stuff:
|
|
61
|
+
instance/
|
|
62
|
+
.webassets-cache
|
|
63
|
+
|
|
64
|
+
# Scrapy stuff:
|
|
65
|
+
.scrapy
|
|
66
|
+
|
|
67
|
+
# Sphinx documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# PyBuilder
|
|
71
|
+
target/
|
|
72
|
+
|
|
73
|
+
# Jupyter Notebook
|
|
74
|
+
.ipynb_checkpoints
|
|
75
|
+
|
|
76
|
+
# IPython
|
|
77
|
+
profile_default/
|
|
78
|
+
ipython_config.py
|
|
79
|
+
|
|
80
|
+
# pyenv
|
|
81
|
+
.python-version
|
|
82
|
+
|
|
83
|
+
# celery beat schedule file
|
|
84
|
+
celerybeat-schedule
|
|
85
|
+
|
|
86
|
+
# SageMath parsed files
|
|
87
|
+
*.sage.py
|
|
88
|
+
|
|
89
|
+
# Environments
|
|
90
|
+
.env
|
|
91
|
+
.venv
|
|
92
|
+
env/
|
|
93
|
+
venv/
|
|
94
|
+
ENV/
|
|
95
|
+
env.bak/
|
|
96
|
+
venv.bak/
|
|
97
|
+
|
|
98
|
+
# Spyder project settings
|
|
99
|
+
.spyderproject
|
|
100
|
+
.spyproject
|
|
101
|
+
|
|
102
|
+
# Rope project settings
|
|
103
|
+
.ropeproject
|
|
104
|
+
|
|
105
|
+
# mkdocs documentation
|
|
106
|
+
/site
|
|
107
|
+
|
|
108
|
+
# mypy
|
|
109
|
+
.mypy_cache/
|
|
110
|
+
.dmypy.json
|
|
111
|
+
dmypy.json
|
|
112
|
+
|
|
113
|
+
# Pyre type checker
|
|
114
|
+
.pyre/
|
|
115
|
+
test.db
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Luis Diego Jiménez Delgado
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: finalsa-common-models
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Common models for Finalsa
|
|
5
|
+
Project-URL: Homepage, https://github.com/finalsa/finalsa-common-models
|
|
6
|
+
Author-email: Luis Jimenez <luis@finalsa.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Luis Diego Jiménez Delgado
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE.md
|
|
29
|
+
Keywords: common,finalsa,models
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: orjson>=3.10.16
|
|
32
|
+
Requires-Dist: pydantic>=2.11.1
|
|
33
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Finalsa Async Models
|
|
37
|
+
|
|
38
|
+
Finalsa Async Models is a Python library designed to simplify the implementation of asynchronous data models. It provides tools to handle asynchronous operations, making it easier to work with modern Python applications that rely on async/await patterns.
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Asynchronous Data Models**: Define and manage data models with full async support.
|
|
43
|
+
- **Ease of Use**: Simplified API for seamless integration into your projects.
|
|
44
|
+
- **Extensibility**: Easily extend and customize models to fit your needs.
|
|
45
|
+
- **Performance**: Optimized for high-performance asynchronous workflows.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
Install the library using pip:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install finalsa-async-models
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
Here's a quick example of how to use Finalsa Async Models:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from finalsa_async_models import AsyncModel
|
|
61
|
+
|
|
62
|
+
class User(AsyncModel):
|
|
63
|
+
async def save(self):
|
|
64
|
+
# Custom save logic
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
# Example usage
|
|
68
|
+
async def main():
|
|
69
|
+
user = User()
|
|
70
|
+
await user.save()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
For detailed documentation, visit the [official documentation](#).
|
|
76
|
+
|
|
77
|
+
## Contributing
|
|
78
|
+
|
|
79
|
+
Contributions are welcome! Please follow the [contribution guidelines](CONTRIBUTING.md).
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
84
|
+
|
|
85
|
+
## Contact
|
|
86
|
+
|
|
87
|
+
For questions or support, please open an issue on the [GitHub repository](#).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Finalsa Async Models
|
|
2
|
+
|
|
3
|
+
Finalsa Async Models is a Python library designed to simplify the implementation of asynchronous data models. It provides tools to handle asynchronous operations, making it easier to work with modern Python applications that rely on async/await patterns.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Asynchronous Data Models**: Define and manage data models with full async support.
|
|
8
|
+
- **Ease of Use**: Simplified API for seamless integration into your projects.
|
|
9
|
+
- **Extensibility**: Easily extend and customize models to fit your needs.
|
|
10
|
+
- **Performance**: Optimized for high-performance asynchronous workflows.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install the library using pip:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install finalsa-async-models
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Here's a quick example of how to use Finalsa Async Models:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from finalsa_async_models import AsyncModel
|
|
26
|
+
|
|
27
|
+
class User(AsyncModel):
|
|
28
|
+
async def save(self):
|
|
29
|
+
# Custom save logic
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
# Example usage
|
|
33
|
+
async def main():
|
|
34
|
+
user = User()
|
|
35
|
+
await user.save()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
For detailed documentation, visit the [official documentation](#).
|
|
41
|
+
|
|
42
|
+
## Contributing
|
|
43
|
+
|
|
44
|
+
Contributions are welcome! Please follow the [contribution guidelines](CONTRIBUTING.md).
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
49
|
+
|
|
50
|
+
## Contact
|
|
51
|
+
|
|
52
|
+
For questions or support, please open an issue on the [GitHub repository](#).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from finalsa.common.models.models import (
|
|
2
|
+
Meta,
|
|
3
|
+
SqsReponse,
|
|
4
|
+
parse_sns_message_attributes,
|
|
5
|
+
parse_sqs_message_attributes,
|
|
6
|
+
to_sqs_message_attributes,
|
|
7
|
+
to_sns_message_attributes,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Meta",
|
|
13
|
+
"SqsReponse",
|
|
14
|
+
"parse_sns_message_attributes",
|
|
15
|
+
"parse_sqs_message_attributes",
|
|
16
|
+
"to_sqs_message_attributes",
|
|
17
|
+
"to_sns_message_attributes",
|
|
18
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .functions import parse_sns_message_attributes, parse_sqs_message_attributes, to_sqs_message_attributes, to_sns_message_attributes
|
|
2
|
+
from .sqs_response import SqsReponse
|
|
3
|
+
from .meta import Meta
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Meta",
|
|
7
|
+
"SqsReponse",
|
|
8
|
+
"parse_sns_message_attributes",
|
|
9
|
+
"parse_sqs_message_attributes",
|
|
10
|
+
"to_sqs_message_attributes",
|
|
11
|
+
"to_sns_message_attributes"
|
|
12
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_sns_message_attributes(attributes: Dict) -> Dict:
|
|
8
|
+
message_attributes = {}
|
|
9
|
+
if not attributes:
|
|
10
|
+
return message_attributes
|
|
11
|
+
for key, value in attributes.items():
|
|
12
|
+
if value['Type'] == 'String':
|
|
13
|
+
message_attributes[key] = value['Value']
|
|
14
|
+
elif value['Type'] == 'Number':
|
|
15
|
+
message_attributes[key] = int(value['Value'])
|
|
16
|
+
elif value['Type'] == 'Binary':
|
|
17
|
+
message_attributes[key] = bytes(value['Value'], 'utf-8')
|
|
18
|
+
return message_attributes
|
|
19
|
+
|
|
20
|
+
def parse_sqs_message_attributes(attributes: Dict) -> Dict:
|
|
21
|
+
message_attributes = {}
|
|
22
|
+
if not attributes:
|
|
23
|
+
return message_attributes
|
|
24
|
+
for key, value in attributes.items():
|
|
25
|
+
if value['DataType'] == 'String':
|
|
26
|
+
message_attributes[key] = value['StringValue']
|
|
27
|
+
elif value['DataType'] == 'Number':
|
|
28
|
+
message_attributes[key] = int(value['StringValue'])
|
|
29
|
+
elif value['DataType'] == 'Binary':
|
|
30
|
+
message_attributes[key] = bytes(value['StringValue'], 'utf-8')
|
|
31
|
+
return message_attributes
|
|
32
|
+
|
|
33
|
+
def to_sqs_message_attributes(attributes: Dict) -> Dict:
|
|
34
|
+
att_dict = {}
|
|
35
|
+
for key, value in attributes.items():
|
|
36
|
+
if isinstance(value, str):
|
|
37
|
+
att_dict[key] = {
|
|
38
|
+
'DataType': 'String', 'StringValue': value}
|
|
39
|
+
elif isinstance(value, Decimal):
|
|
40
|
+
att_dict[key] = {
|
|
41
|
+
'DataType': 'Number', 'StringValue': str(value)}
|
|
42
|
+
elif isinstance(value, UUID):
|
|
43
|
+
att_dict[key] = {
|
|
44
|
+
'DataType': 'String', 'StringValue': str(value)}
|
|
45
|
+
elif isinstance(value, datetime):
|
|
46
|
+
att_dict[key] = {
|
|
47
|
+
'DataType': 'String', 'StringValue': value.isoformat()}
|
|
48
|
+
elif isinstance(value, int):
|
|
49
|
+
att_dict[key] = {
|
|
50
|
+
'DataType': 'Number', 'StringValue': str(value)}
|
|
51
|
+
elif isinstance(value, bytes):
|
|
52
|
+
att_dict[key] = {
|
|
53
|
+
'DataType': 'Binary', 'BinaryValue': value}
|
|
54
|
+
return att_dict
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def to_sns_message_attributes(attributes: Dict) -> Dict:
|
|
59
|
+
att_dict = {}
|
|
60
|
+
for key, value in attributes.items():
|
|
61
|
+
if isinstance(value, str):
|
|
62
|
+
att_dict[key] = {
|
|
63
|
+
'Type': 'String', 'Value': value}
|
|
64
|
+
elif isinstance(value, Decimal):
|
|
65
|
+
att_dict[key] = {
|
|
66
|
+
'Type': 'Number', 'Value': str(value)}
|
|
67
|
+
elif isinstance(value, UUID):
|
|
68
|
+
att_dict[key] = {
|
|
69
|
+
'Type': 'String', 'Value': str(value)}
|
|
70
|
+
elif isinstance(value, datetime):
|
|
71
|
+
att_dict[key] = {
|
|
72
|
+
'Type': 'String', 'Value': value.isoformat()}
|
|
73
|
+
elif isinstance(value, int):
|
|
74
|
+
att_dict[key] = {
|
|
75
|
+
'Type': 'Number', 'Value': str(value)}
|
|
76
|
+
elif isinstance(value, bytes):
|
|
77
|
+
att_dict[key] = {
|
|
78
|
+
'Type': 'Binary', 'Value': value}
|
|
79
|
+
return att_dict
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Dict, Optional, Union
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from orjson import loads
|
|
4
|
+
from .functions import parse_sqs_message_attributes, parse_sns_message_attributes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SqsReponse(BaseModel):
|
|
8
|
+
message_id: str
|
|
9
|
+
receipt_handle: str
|
|
10
|
+
md5_of_body: str
|
|
11
|
+
body: str
|
|
12
|
+
attributes: Optional[Dict] = {}
|
|
13
|
+
topic: Optional[str] = ""
|
|
14
|
+
md5_of_message_attributes: Optional[str] = ""
|
|
15
|
+
message_attributes: Optional[Dict] = {}
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def __is_sns_message__(content: Dict) -> bool:
|
|
19
|
+
return 'Type' in content and content['Type'] == 'Notification'
|
|
20
|
+
|
|
21
|
+
def parse_from_sns(self) -> Dict:
|
|
22
|
+
payload = loads(self.body)
|
|
23
|
+
if self.__is_sns_message__(payload):
|
|
24
|
+
return self.__parse_from_sns__(payload)
|
|
25
|
+
raise ValueError('The message is not a SNS message')
|
|
26
|
+
|
|
27
|
+
def __parse_from_sns__(self, payload: Dict) -> Union[str, Dict]:
|
|
28
|
+
self.topic = str(payload['TopicArn'].split(':')[-1]).lower()
|
|
29
|
+
self.message_attributes = parse_sns_message_attributes(
|
|
30
|
+
payload.get('MessageAttributes', {}))
|
|
31
|
+
try:
|
|
32
|
+
return loads(payload['Message'])
|
|
33
|
+
except Exception:
|
|
34
|
+
return payload['Message']
|
|
35
|
+
|
|
36
|
+
def parse(self) -> Optional[Dict]:
|
|
37
|
+
content = loads(self.body)
|
|
38
|
+
if self.__is_sns_message__(content):
|
|
39
|
+
content = self.__parse_from_sns__(content)
|
|
40
|
+
return content
|
|
41
|
+
|
|
42
|
+
def get_payload(self) -> Union[str, Dict]:
|
|
43
|
+
try:
|
|
44
|
+
content = loads(self.body)
|
|
45
|
+
except Exception:
|
|
46
|
+
return self.body
|
|
47
|
+
if self.__is_sns_message__(content):
|
|
48
|
+
content = self.__parse_from_sns__(content)
|
|
49
|
+
return content
|
|
50
|
+
return loads(self.body)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_boto_response(cls, response: Dict):
|
|
54
|
+
return cls(
|
|
55
|
+
message_id=response['MessageId'],
|
|
56
|
+
receipt_handle=response['ReceiptHandle'],
|
|
57
|
+
md5_of_body=response.get('MD5OfBody', ""),
|
|
58
|
+
body=response['Body'],
|
|
59
|
+
attributes=response['Attributes'],
|
|
60
|
+
md5_of_message_attributes=response.get(
|
|
61
|
+
'MD5OfMessageAttributes', ''),
|
|
62
|
+
message_attributes=parse_sqs_message_attributes(
|
|
63
|
+
response.get('MessageAttributes', {}))
|
|
64
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "finalsa-common-models"
|
|
3
|
+
version = "2.0.1"
|
|
4
|
+
description = "Common models for Finalsa"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"orjson>=3.10.16",
|
|
9
|
+
"pydantic>=2.11.1",
|
|
10
|
+
"python-dateutil>=2.9.0.post0",
|
|
11
|
+
]
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Luis Jimenez", email = "luis@finalsa.com"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["finalsa", "common", "models"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/finalsa/finalsa-common-models"
|
|
19
|
+
|
|
20
|
+
[project.license]
|
|
21
|
+
name = "MIT"
|
|
22
|
+
file = "LICENSE.md"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["hatchling"]
|
|
26
|
+
build-backend = "hatchling.build"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.sdist]
|
|
30
|
+
include = [
|
|
31
|
+
"finalsa/common/models/*.py",
|
|
32
|
+
"finalsa/common/models/models/*.py"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
include = [
|
|
37
|
+
"finalsa/common/models/*.py",
|
|
38
|
+
"finalsa/common/models/models/*.py"
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
test = [
|
|
44
|
+
"pytest>=8.3.5",
|
|
45
|
+
]
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: finalsa-common-models
|
|
3
|
-
Version: 1.0.1
|
|
4
|
-
Summary: An utils package for using finalsa common models.
|
|
5
|
-
Home-page: https://github.com/finalsa/finalsa-common-models
|
|
6
|
-
Author: Luis Jimenez
|
|
7
|
-
Author-email: luis@finalsa.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: dynamodb
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Requires-Python: >=3.10
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE.md
|
|
22
|
-
Requires-Dist: boto3>=1.20.3
|
|
23
|
-
Requires-Dist: pydantic>=2.5.2
|
|
File without changes
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from finalsa.common.models.models import (
|
|
2
|
-
SqsMessage,
|
|
3
|
-
SqsReponse,
|
|
4
|
-
parse_message_attributes,
|
|
5
|
-
to_message_attributes
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
__version__ = "1.0.1"
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"SqsMessage",
|
|
12
|
-
"SqsReponse",
|
|
13
|
-
"parse_message_attributes",
|
|
14
|
-
"to_message_attributes",
|
|
15
|
-
]
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from typing import Dict
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from decimal import Decimal
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def parse_message_attributes(attributes: Dict) -> Dict:
|
|
8
|
-
message_attributes = {}
|
|
9
|
-
if not attributes:
|
|
10
|
-
return message_attributes
|
|
11
|
-
for key, value in attributes.items():
|
|
12
|
-
if value['Type'] == 'String':
|
|
13
|
-
message_attributes[key] = value['Value']
|
|
14
|
-
elif value['Type'] == 'Number':
|
|
15
|
-
message_attributes[key] = int(value['Value'])
|
|
16
|
-
elif value['Type'] == 'Binary':
|
|
17
|
-
message_attributes[key] = bytes(value['Value'], 'utf-8')
|
|
18
|
-
return message_attributes
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def to_message_attributes(attributes: Dict) -> Dict:
|
|
22
|
-
att_dict = {}
|
|
23
|
-
for key, value in attributes.items():
|
|
24
|
-
if isinstance(value, str):
|
|
25
|
-
att_dict[key] = {
|
|
26
|
-
'DataType': 'String', 'StringValue': value}
|
|
27
|
-
elif isinstance(value, Decimal):
|
|
28
|
-
att_dict[key] = {
|
|
29
|
-
'DataType': 'Number', 'StringValue': str(value)}
|
|
30
|
-
elif isinstance(value, UUID):
|
|
31
|
-
att_dict[key] = {
|
|
32
|
-
'DataType': 'String', 'StringValue': str(value)}
|
|
33
|
-
elif isinstance(value, datetime):
|
|
34
|
-
att_dict[key] = {
|
|
35
|
-
'DataType': 'String', 'StringValue': value.isoformat()}
|
|
36
|
-
elif isinstance(value, int):
|
|
37
|
-
att_dict[key] = {
|
|
38
|
-
'DataType': 'Number', 'StringValue': str(value)}
|
|
39
|
-
elif isinstance(value, bytes):
|
|
40
|
-
att_dict[key] = {
|
|
41
|
-
'DataType': 'Binary', 'BinaryValue': value}
|
|
42
|
-
return att_dict
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timezone
|
|
2
|
-
from typing import Optional, Union, Dict
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
from json import loads
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class SqsMessage(BaseModel):
|
|
9
|
-
id: UUID
|
|
10
|
-
topic: str
|
|
11
|
-
payload: Union[str, Dict]
|
|
12
|
-
correlation_id: str
|
|
13
|
-
timestamp: Optional[datetime] = datetime.now(timezone.utc)
|
|
14
|
-
|
|
15
|
-
def get_payload(self) -> Dict:
|
|
16
|
-
if isinstance(self.payload, str):
|
|
17
|
-
self.payload = loads(self.payload)
|
|
18
|
-
return self.payload
|
|
19
|
-
|
|
20
|
-
def to_dict(self):
|
|
21
|
-
return {
|
|
22
|
-
'id': str(self.id),
|
|
23
|
-
'topic': self.topic,
|
|
24
|
-
'payload': self.payload,
|
|
25
|
-
'correlation_id': self.correlation_id,
|
|
26
|
-
'timestamp': self.timestamp.isoformat()
|
|
27
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timezone
|
|
2
|
-
from typing import Dict, Optional, Union
|
|
3
|
-
from uuid import UUID, uuid4
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
from json import loads
|
|
6
|
-
from .sqs_message import SqsMessage
|
|
7
|
-
from .functions import parse_message_attributes
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SqsReponse(BaseModel):
|
|
11
|
-
message_id: str
|
|
12
|
-
receipt_handle: str
|
|
13
|
-
md5_of_body: str
|
|
14
|
-
body: str
|
|
15
|
-
attributes: Optional[Dict] = {}
|
|
16
|
-
topic: Optional[str] = ""
|
|
17
|
-
md5_of_message_attributes: Optional[str] = ""
|
|
18
|
-
message_attributes: Optional[Dict] = {}
|
|
19
|
-
|
|
20
|
-
@staticmethod
|
|
21
|
-
def correlation_id_from_attributes(attributes: Dict) -> Optional[str]:
|
|
22
|
-
correlation_id = attributes.get('correlation_id', None)
|
|
23
|
-
if not correlation_id:
|
|
24
|
-
return None
|
|
25
|
-
if isinstance(correlation_id, str):
|
|
26
|
-
return correlation_id
|
|
27
|
-
if isinstance(correlation_id, dict) and 'Type' in correlation_id and 'Value' in correlation_id:
|
|
28
|
-
return correlation_id["Value"]
|
|
29
|
-
return None
|
|
30
|
-
|
|
31
|
-
def get_correlation_id(self, payload: Optional[Dict] = {}) -> Union[str, UUID]:
|
|
32
|
-
correlation_id = self.correlation_id_from_attributes(self.message_attributes)
|
|
33
|
-
if correlation_id:
|
|
34
|
-
return correlation_id
|
|
35
|
-
correlation_id = self.correlation_id_from_attributes(self.attributes)
|
|
36
|
-
if correlation_id:
|
|
37
|
-
return correlation_id
|
|
38
|
-
if 'correlation_id' in payload:
|
|
39
|
-
return payload['correlation_id']
|
|
40
|
-
return str(uuid4())
|
|
41
|
-
|
|
42
|
-
@staticmethod
|
|
43
|
-
def __is_sns_message__(content: Dict) -> bool:
|
|
44
|
-
return 'Type' in content and content['Type'] == 'Notification'
|
|
45
|
-
|
|
46
|
-
@staticmethod
|
|
47
|
-
def __is_sqs_message__(content: Dict) -> bool:
|
|
48
|
-
return ('id' in content and
|
|
49
|
-
'topic' in content and
|
|
50
|
-
'payload' in content)
|
|
51
|
-
|
|
52
|
-
def parse_from_sns(self) -> Dict:
|
|
53
|
-
payload = loads(self.body)
|
|
54
|
-
if self.__is_sns_message__(payload):
|
|
55
|
-
return self.__parse_from_sns__(payload)
|
|
56
|
-
raise ValueError('The message is not a SNS message')
|
|
57
|
-
|
|
58
|
-
def __parse_from_sns__(self, payload: Dict) -> Union[str, Dict]:
|
|
59
|
-
self.topic = str(payload['TopicArn'].split(':')[-1]).lower()
|
|
60
|
-
self.message_attributes = parse_message_attributes(
|
|
61
|
-
payload.get('MessageAttributes', {}))
|
|
62
|
-
try:
|
|
63
|
-
return loads(payload['Message'])
|
|
64
|
-
except Exception:
|
|
65
|
-
return payload['Message']
|
|
66
|
-
|
|
67
|
-
def parse(self) -> Optional[Dict]:
|
|
68
|
-
content = loads(self.body)
|
|
69
|
-
if self.__is_sns_message__(content):
|
|
70
|
-
content = self.__parse_from_sns__(content)
|
|
71
|
-
return content
|
|
72
|
-
|
|
73
|
-
def __get_sqs_message__(self, content: Union[str, Dict]) -> SqsMessage:
|
|
74
|
-
|
|
75
|
-
if isinstance(content, dict) and self.__is_sqs_message__(content):
|
|
76
|
-
if 'correlation_id' not in content:
|
|
77
|
-
content['correlation_id'] = str(self.get_correlation_id(content))
|
|
78
|
-
return SqsMessage(
|
|
79
|
-
id=UUID(content['id']),
|
|
80
|
-
topic=content['topic'],
|
|
81
|
-
payload=content['payload'],
|
|
82
|
-
correlation_id=content['correlation_id'],
|
|
83
|
-
timestamp=content.get('timestamp', datetime.now(timezone.utc).isoformat())
|
|
84
|
-
)
|
|
85
|
-
return SqsMessage(
|
|
86
|
-
id=uuid4(),
|
|
87
|
-
topic=self.topic,
|
|
88
|
-
payload=content,
|
|
89
|
-
correlation_id=self.get_correlation_id(content),
|
|
90
|
-
timestamp=datetime.now(timezone.utc).isoformat()
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
def get_sqs_message(self) -> SqsMessage:
|
|
94
|
-
try:
|
|
95
|
-
content = loads(self.body)
|
|
96
|
-
except Exception:
|
|
97
|
-
return self.__get_sqs_message__(self.body)
|
|
98
|
-
if self.__is_sns_message__(content):
|
|
99
|
-
content = self.__parse_from_sns__(content)
|
|
100
|
-
return self.__get_sqs_message__(content)
|
|
101
|
-
|
|
102
|
-
@classmethod
|
|
103
|
-
def from_boto_response(cls, response: Dict):
|
|
104
|
-
return cls(
|
|
105
|
-
message_id=response['MessageId'],
|
|
106
|
-
receipt_handle=response['ReceiptHandle'],
|
|
107
|
-
md5_of_body=response.get('MD5OfBody', ""),
|
|
108
|
-
body=response['Body'],
|
|
109
|
-
attributes=response['Attributes'],
|
|
110
|
-
md5_of_message_attributes=response.get(
|
|
111
|
-
'MD5OfMessageAttributes', ''),
|
|
112
|
-
message_attributes=parse_message_attributes(
|
|
113
|
-
response.get('MessageAttributes', {}))
|
|
114
|
-
)
|
|
File without changes
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: finalsa-common-models
|
|
3
|
-
Version: 1.0.1
|
|
4
|
-
Summary: An utils package for using finalsa common models.
|
|
5
|
-
Home-page: https://github.com/finalsa/finalsa-common-models
|
|
6
|
-
Author: Luis Jimenez
|
|
7
|
-
Author-email: luis@finalsa.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: dynamodb
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Requires-Python: >=3.10
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE.md
|
|
22
|
-
Requires-Dist: boto3>=1.20.3
|
|
23
|
-
Requires-Dist: pydantic>=2.5.2
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
LICENSE.md
|
|
2
|
-
README.md
|
|
3
|
-
setup.cfg
|
|
4
|
-
setup.py
|
|
5
|
-
finalsa/common/models/__init__.py
|
|
6
|
-
finalsa/common/models/py.typed
|
|
7
|
-
finalsa/common/models/models/__init__.py
|
|
8
|
-
finalsa/common/models/models/functions.py
|
|
9
|
-
finalsa/common/models/models/sqs_message.py
|
|
10
|
-
finalsa/common/models/models/sqs_response.py
|
|
11
|
-
finalsa_common_models.egg-info/PKG-INFO
|
|
12
|
-
finalsa_common_models.egg-info/SOURCES.txt
|
|
13
|
-
finalsa_common_models.egg-info/dependency_links.txt
|
|
14
|
-
finalsa_common_models.egg-info/requires.txt
|
|
15
|
-
finalsa_common_models.egg-info/top_level.txt
|
|
16
|
-
finalsa_common_models.egg-info/zip-safe
|
|
17
|
-
tests/test_client.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
import os
|
|
5
|
-
import re
|
|
6
|
-
|
|
7
|
-
from setuptools import setup
|
|
8
|
-
|
|
9
|
-
PACKAGE = "finalsa-common-models"
|
|
10
|
-
URL = "https://github.com/finalsa/finalsa-common-models"
|
|
11
|
-
PACKAGE_FOLDER = "finalsa/common/models"
|
|
12
|
-
|
|
13
|
-
def get_version(package):
|
|
14
|
-
"""
|
|
15
|
-
Return package version as listed in `__version__` in `init.py`.
|
|
16
|
-
"""
|
|
17
|
-
with open(os.path.join(package, "__init__.py")) as f:
|
|
18
|
-
return re.search("__version__ = ['\"]([^'\"]+)['\"]", f.read()).group(1)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_long_description():
|
|
22
|
-
"""
|
|
23
|
-
Return the README.
|
|
24
|
-
"""
|
|
25
|
-
with open("README.md", encoding="utf8") as f:
|
|
26
|
-
return f.read()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_packages(package):
|
|
30
|
-
"""
|
|
31
|
-
Return root package and all sub-packages.
|
|
32
|
-
"""
|
|
33
|
-
return [
|
|
34
|
-
dirpath
|
|
35
|
-
for dirpath, dirnames, filenames in os.walk(package)
|
|
36
|
-
if os.path.exists(os.path.join(dirpath, "__init__.py"))
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
setup(
|
|
41
|
-
name=PACKAGE,
|
|
42
|
-
version=get_version(PACKAGE_FOLDER),
|
|
43
|
-
url=URL,
|
|
44
|
-
license="MIT",
|
|
45
|
-
description="An utils package for using finalsa common models.",
|
|
46
|
-
long_description=get_long_description(),
|
|
47
|
-
long_description_content_type="text/markdown",
|
|
48
|
-
keywords=[
|
|
49
|
-
"dynamodb",
|
|
50
|
-
],
|
|
51
|
-
author="Luis Jimenez",
|
|
52
|
-
author_email="luis@finalsa.com",
|
|
53
|
-
packages=get_packages(PACKAGE_FOLDER),
|
|
54
|
-
package_data={PACKAGE: ["py.typed"]},
|
|
55
|
-
include_package_data=True,
|
|
56
|
-
zip_safe=True,
|
|
57
|
-
python_requires=">=3.10",
|
|
58
|
-
data_files=[("", ["LICENSE.md"])],
|
|
59
|
-
install_requires=[
|
|
60
|
-
"boto3>=1.20.3",
|
|
61
|
-
"pydantic>=2.5.2",
|
|
62
|
-
],
|
|
63
|
-
extras_require={
|
|
64
|
-
|
|
65
|
-
},
|
|
66
|
-
classifiers=[
|
|
67
|
-
"Intended Audience :: Developers",
|
|
68
|
-
"License :: OSI Approved :: BSD License",
|
|
69
|
-
"Operating System :: OS Independent",
|
|
70
|
-
"Topic :: Internet :: WWW/HTTP",
|
|
71
|
-
"Programming Language :: Python :: 3",
|
|
72
|
-
"Programming Language :: Python :: 3.7",
|
|
73
|
-
"Programming Language :: Python :: 3.8",
|
|
74
|
-
"Programming Language :: Python :: 3.9",
|
|
75
|
-
"Programming Language :: Python :: 3.10",
|
|
76
|
-
],
|
|
77
|
-
)
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
from finalsa.common.models import (
|
|
2
|
-
SqsMessage, SqsReponse, parse_message_attributes,
|
|
3
|
-
to_message_attributes, __version__)
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
import uuid
|
|
7
|
-
import datetime
|
|
8
|
-
import decimal
|
|
9
|
-
|
|
10
|
-
from json import dumps
|
|
11
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_version():
|
|
15
|
-
assert __version__ is not None
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_parse_message_attributes():
|
|
19
|
-
attributes = {
|
|
20
|
-
'string': {'Type': 'String', 'Value': 'test'},
|
|
21
|
-
'number': {'Type': 'Number', 'Value': '1'},
|
|
22
|
-
'binary': {'Type': 'Binary', 'Value': 'test'}
|
|
23
|
-
}
|
|
24
|
-
result = parse_message_attributes(attributes)
|
|
25
|
-
assert result['string'] == 'test'
|
|
26
|
-
assert result['number'] == 1
|
|
27
|
-
assert result['binary'] == b'test'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_sqs_message():
|
|
31
|
-
message = SqsMessage(
|
|
32
|
-
id='123e4567-e89b-12d3-a456-426614174000',
|
|
33
|
-
topic='test',
|
|
34
|
-
payload='{"test": "test"}',
|
|
35
|
-
correlation_id='123e4567-e89b-12d3-a456-426614174000'
|
|
36
|
-
)
|
|
37
|
-
assert str(message.id) == '123e4567-e89b-12d3-a456-426614174000'
|
|
38
|
-
assert message.topic == 'test'
|
|
39
|
-
assert message.get_payload() == {'test': 'test'}
|
|
40
|
-
assert message.correlation_id == '123e4567-e89b-12d3-a456-426614174000'
|
|
41
|
-
assert message.timestamp is not None
|
|
42
|
-
assert message.to_dict() == {
|
|
43
|
-
'id': '123e4567-e89b-12d3-a456-426614174000',
|
|
44
|
-
'topic': 'test',
|
|
45
|
-
'payload': {'test': 'test'},
|
|
46
|
-
'correlation_id': '123e4567-e89b-12d3-a456-426614174000',
|
|
47
|
-
'timestamp': message.timestamp.isoformat()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def test_sqs_message_str():
|
|
52
|
-
message = SqsMessage(
|
|
53
|
-
id='123e4567-e89b-12d3-a456-426614174000',
|
|
54
|
-
topic='test',
|
|
55
|
-
payload='{"test: "test"}',
|
|
56
|
-
correlation_id='123e4567-e89b-12d3-a456-426614174000'
|
|
57
|
-
)
|
|
58
|
-
assert str(message.id) == '123e4567-e89b-12d3-a456-426614174000'
|
|
59
|
-
assert message.topic == 'test'
|
|
60
|
-
assert message.correlation_id == '123e4567-e89b-12d3-a456-426614174000'
|
|
61
|
-
assert message.timestamp is not None
|
|
62
|
-
assert message.to_dict() == {
|
|
63
|
-
'id': '123e4567-e89b-12d3-a456-426614174000',
|
|
64
|
-
'topic': 'test',
|
|
65
|
-
'payload': '{"test: "test"}',
|
|
66
|
-
'correlation_id': '123e4567-e89b-12d3-a456-426614174000',
|
|
67
|
-
'timestamp': message.timestamp.isoformat()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_sqs_response():
|
|
72
|
-
response = SqsReponse(
|
|
73
|
-
message_id='test',
|
|
74
|
-
receipt_handle='test',
|
|
75
|
-
md5_of_body='test',
|
|
76
|
-
body='test',
|
|
77
|
-
attributes={'test': 'test',
|
|
78
|
-
'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
79
|
-
topic='test',
|
|
80
|
-
md5_of_message_attributes='test',
|
|
81
|
-
message_attributes={'correlation_id': {'Type': 'String',
|
|
82
|
-
'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
assert response.body == 'test'
|
|
86
|
-
assert str(response.get_correlation_id()) == '123e4567-e89b-12d3-a456-426614174000'
|
|
87
|
-
assert response.topic == 'test'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_sqs_response_from_boto_response():
|
|
91
|
-
boto_response = {
|
|
92
|
-
'MessageId': 'test',
|
|
93
|
-
'ReceiptHandle': 'test',
|
|
94
|
-
'MD5OfBody': 'test',
|
|
95
|
-
'Body': 'test',
|
|
96
|
-
'Attributes': {'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
97
|
-
'MessageAttributes': {'correlation_id': {'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
response = SqsReponse.from_boto_response(boto_response)
|
|
102
|
-
assert response.body == 'test'
|
|
103
|
-
assert str(response.get_correlation_id()) == '123e4567-e89b-12d3-a456-426614174000'
|
|
104
|
-
assert response.topic == ''
|
|
105
|
-
assert response.message_attributes == {
|
|
106
|
-
'correlation_id': "123e4567-e89b-12d3-a456-426614174000"}
|
|
107
|
-
assert response.attributes == {
|
|
108
|
-
'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def test_sqs_response_no_correlation_id():
|
|
112
|
-
response = SqsReponse(
|
|
113
|
-
message_id='test',
|
|
114
|
-
receipt_handle='test',
|
|
115
|
-
md5_of_body='test',
|
|
116
|
-
body='test',
|
|
117
|
-
attributes={'test': 'test'},
|
|
118
|
-
topic='test',
|
|
119
|
-
md5_of_message_attributes='test',
|
|
120
|
-
)
|
|
121
|
-
assert response.body == 'test'
|
|
122
|
-
assert response.get_correlation_id() is not None
|
|
123
|
-
assert response.topic == 'test'
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def test_sqs_message_from_dict():
|
|
127
|
-
|
|
128
|
-
message = SqsMessage(
|
|
129
|
-
id='123e4567-e89b-12d3-a456-426614174000',
|
|
130
|
-
topic='test',
|
|
131
|
-
payload='{"test": "test"}',
|
|
132
|
-
correlation_id='123e4567-e89b-12d3-a456-426614174000'
|
|
133
|
-
)
|
|
134
|
-
message_dict = message.to_dict()
|
|
135
|
-
assert str(message.id) == message_dict['id']
|
|
136
|
-
assert message.topic == message_dict['topic']
|
|
137
|
-
assert message.get_payload() == {'test': 'test'}
|
|
138
|
-
assert message.correlation_id == message_dict['correlation_id']
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def test_to_message_attributes():
|
|
142
|
-
attributes = {
|
|
143
|
-
'string': 'test',
|
|
144
|
-
'number': 1,
|
|
145
|
-
'binary': b'test',
|
|
146
|
-
'uuid': uuid.uuid4(),
|
|
147
|
-
'datetime': datetime.datetime.now(),
|
|
148
|
-
'decimal': decimal.Decimal('1.0')
|
|
149
|
-
}
|
|
150
|
-
result = to_message_attributes(attributes)
|
|
151
|
-
assert result['string']['DataType'] == 'String'
|
|
152
|
-
assert result['string']['StringValue'] == 'test'
|
|
153
|
-
assert result['number']['DataType'] == 'Number'
|
|
154
|
-
assert result['number']['StringValue'] == '1'
|
|
155
|
-
assert result['binary']['DataType'] == 'Binary'
|
|
156
|
-
assert result['binary']['BinaryValue'] == b'test'
|
|
157
|
-
assert result['uuid']['DataType'] == 'String'
|
|
158
|
-
assert result['uuid']['StringValue'] == str(attributes['uuid'])
|
|
159
|
-
assert result['datetime']['DataType'] == 'String'
|
|
160
|
-
assert result['datetime']['StringValue'] == attributes['datetime'].isoformat()
|
|
161
|
-
assert result['decimal']['DataType'] == 'Number'
|
|
162
|
-
assert result['decimal']['StringValue'] == '1.0'
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def test_parse_from_sns_response():
|
|
166
|
-
real_message_body = {
|
|
167
|
-
"test": "test"
|
|
168
|
-
}
|
|
169
|
-
body = dumps({
|
|
170
|
-
'Type': 'Notification',
|
|
171
|
-
'TopicArn': 'mytopic',
|
|
172
|
-
'Message': dumps(real_message_body),
|
|
173
|
-
'MessageAttributes': {'correlation_id': {
|
|
174
|
-
'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'
|
|
175
|
-
}}
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
boto_response = {
|
|
179
|
-
'MessageId': 'test',
|
|
180
|
-
'ReceiptHandle': 'test',
|
|
181
|
-
'MD5OfBody': 'test',
|
|
182
|
-
'Body': body,
|
|
183
|
-
'Attributes': {'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
184
|
-
'MessageAttributes': {'correlation_id': {'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
response = SqsReponse.from_boto_response(boto_response)
|
|
189
|
-
assert response.body == body
|
|
190
|
-
assert response.get_correlation_id() == '123e4567-e89b-12d3-a456-426614174000'
|
|
191
|
-
assert response.topic == ''
|
|
192
|
-
assert response.message_attributes == {
|
|
193
|
-
'correlation_id': "123e4567-e89b-12d3-a456-426614174000"}
|
|
194
|
-
assert response.attributes == {
|
|
195
|
-
'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'}
|
|
196
|
-
assert response.parse_from_sns() == real_message_body
|
|
197
|
-
assert response.parse() == real_message_body
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def test_parse_from_sns_response_sqs_message():
|
|
201
|
-
real_message_body = {
|
|
202
|
-
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
203
|
-
}
|
|
204
|
-
sqs_message_payload = {
|
|
205
|
-
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
206
|
-
"topic": "test",
|
|
207
|
-
"payload": dumps(real_message_body),
|
|
208
|
-
"correlation_id": "123e4567-e89b-12d3-a456-426614174000",
|
|
209
|
-
"timestamp": datetime.datetime.now().isoformat()
|
|
210
|
-
}
|
|
211
|
-
body = dumps({
|
|
212
|
-
'Type': 'Notification',
|
|
213
|
-
'TopicArn': 'mytopic',
|
|
214
|
-
'Message': dumps(sqs_message_payload),
|
|
215
|
-
'MessageAttributes': {'correlation_id': {
|
|
216
|
-
'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'
|
|
217
|
-
}}
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
boto_response = {
|
|
221
|
-
'MessageId': 'test',
|
|
222
|
-
'ReceiptHandle': 'test',
|
|
223
|
-
'MD5OfBody': 'test',
|
|
224
|
-
'Body': body,
|
|
225
|
-
'Attributes': {'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
226
|
-
'MessageAttributes': {'correlation_id': {'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
response = SqsReponse.from_boto_response(boto_response)
|
|
230
|
-
assert response.body == body
|
|
231
|
-
assert response.get_correlation_id() == '123e4567-e89b-12d3-a456-426614174000'
|
|
232
|
-
assert response.topic == ''
|
|
233
|
-
assert response.message_attributes == {
|
|
234
|
-
'correlation_id': "123e4567-e89b-12d3-a456-426614174000"}
|
|
235
|
-
assert response.attributes == {
|
|
236
|
-
'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'}
|
|
237
|
-
sqs_message = response.get_sqs_message()
|
|
238
|
-
assert sqs_message.id == uuid.UUID(sqs_message_payload['id'])
|
|
239
|
-
assert sqs_message.topic == sqs_message_payload['topic']
|
|
240
|
-
assert sqs_message.get_payload() == real_message_body
|
|
241
|
-
assert sqs_message.correlation_id == sqs_message_payload['correlation_id']
|
|
242
|
-
assert sqs_message.timestamp.isoformat() == sqs_message_payload['timestamp']
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def test_parse_from_sqs_response_sqs_message():
|
|
246
|
-
real_message_body = {
|
|
247
|
-
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
248
|
-
}
|
|
249
|
-
sqs_message_payload = {
|
|
250
|
-
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
251
|
-
"topic": "test",
|
|
252
|
-
"payload": dumps(real_message_body),
|
|
253
|
-
"correlation_id": "123e4567-e89b-12d3-a456-426614174000",
|
|
254
|
-
"timestamp": datetime.datetime.now().isoformat()
|
|
255
|
-
}
|
|
256
|
-
body = dumps(sqs_message_payload)
|
|
257
|
-
boto_response = {
|
|
258
|
-
'MessageId': 'test',
|
|
259
|
-
'ReceiptHandle': 'test',
|
|
260
|
-
'MD5OfBody': 'test',
|
|
261
|
-
'Body': body,
|
|
262
|
-
'Attributes': {'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
263
|
-
'MessageAttributes': {'correlation_id': {'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
response = SqsReponse.from_boto_response(boto_response)
|
|
267
|
-
assert response.body == body
|
|
268
|
-
assert response.get_correlation_id() == '123e4567-e89b-12d3-a456-426614174000'
|
|
269
|
-
assert response.topic == ''
|
|
270
|
-
assert response.message_attributes == {
|
|
271
|
-
'correlation_id': "123e4567-e89b-12d3-a456-426614174000"}
|
|
272
|
-
assert response.attributes == {
|
|
273
|
-
'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'}
|
|
274
|
-
sqs_message = response.get_sqs_message()
|
|
275
|
-
assert sqs_message.id == uuid.UUID(sqs_message_payload['id'])
|
|
276
|
-
assert sqs_message.topic == sqs_message_payload['topic']
|
|
277
|
-
assert sqs_message.get_payload() == real_message_body
|
|
278
|
-
assert sqs_message.correlation_id == sqs_message_payload['correlation_id']
|
|
279
|
-
assert sqs_message.timestamp.isoformat() == sqs_message_payload['timestamp']
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def test_parse_from_sns_response_non_valid_sqs_message():
|
|
283
|
-
real_message_body = {
|
|
284
|
-
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
body = dumps({
|
|
288
|
-
'Type': 'Notification',
|
|
289
|
-
'TopicArn': 'mytopic',
|
|
290
|
-
'Message': dumps(real_message_body),
|
|
291
|
-
'MessageAttributes': {'correlation_id': {
|
|
292
|
-
'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'
|
|
293
|
-
}}
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
boto_response = {
|
|
297
|
-
'MessageId': 'test',
|
|
298
|
-
'ReceiptHandle': 'test',
|
|
299
|
-
'MD5OfBody': 'test',
|
|
300
|
-
'Body': body,
|
|
301
|
-
'Attributes': {'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
302
|
-
'MessageAttributes': {'correlation_id': {'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
response = SqsReponse.from_boto_response(boto_response)
|
|
306
|
-
assert response.body == body
|
|
307
|
-
assert response.get_correlation_id() == '123e4567-e89b-12d3-a456-426614174000'
|
|
308
|
-
assert response.topic == ''
|
|
309
|
-
assert response.message_attributes == {
|
|
310
|
-
'correlation_id': "123e4567-e89b-12d3-a456-426614174000"}
|
|
311
|
-
assert response.attributes == {
|
|
312
|
-
'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'}
|
|
313
|
-
sqs_message = response.get_sqs_message()
|
|
314
|
-
assert sqs_message.id is not None
|
|
315
|
-
assert sqs_message.get_payload() == real_message_body
|
|
316
|
-
assert sqs_message.correlation_id == '123e4567-e89b-12d3-a456-426614174000'
|
|
317
|
-
assert sqs_message.timestamp is not None
|
|
318
|
-
assert sqs_message.topic == 'mytopic'
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
def test_parse_from_sns_response_non_valid_str_sqs_message():
|
|
323
|
-
real_message_body = "a"
|
|
324
|
-
body = dumps({
|
|
325
|
-
'Type': 'Notification',
|
|
326
|
-
'TopicArn': 'mytopic',
|
|
327
|
-
'Message': real_message_body,
|
|
328
|
-
'MessageAttributes': {'correlation_id': {
|
|
329
|
-
'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'
|
|
330
|
-
}}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
boto_response = {
|
|
334
|
-
'MessageId': 'test',
|
|
335
|
-
'ReceiptHandle': 'test',
|
|
336
|
-
'MD5OfBody': 'test',
|
|
337
|
-
'Body': body,
|
|
338
|
-
'Attributes': {'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'},
|
|
339
|
-
'MessageAttributes': {'correlation_id': {'Type': 'String', 'Value': '123e4567-e89b-12d3-a456-426614174000'}}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
response = SqsReponse.from_boto_response(boto_response)
|
|
343
|
-
assert response.body == body
|
|
344
|
-
assert response.get_correlation_id() == '123e4567-e89b-12d3-a456-426614174000'
|
|
345
|
-
assert response.topic == ''
|
|
346
|
-
assert response.message_attributes == {
|
|
347
|
-
'correlation_id': "123e4567-e89b-12d3-a456-426614174000"}
|
|
348
|
-
assert response.attributes == {
|
|
349
|
-
'test': 'test', 'correlation_id': '123e4567-e89b-12d3-a456-426614174000'}
|
|
350
|
-
sqs_message = response.get_sqs_message()
|
|
351
|
-
assert sqs_message.id is not None
|
|
352
|
-
assert sqs_message.payload == real_message_body
|
|
353
|
-
assert sqs_message.correlation_id == '123e4567-e89b-12d3-a456-426614174000'
|
|
354
|
-
assert sqs_message.timestamp is not None
|
|
355
|
-
assert sqs_message.topic == 'mytopic'
|