finalsa-common-models 1.0.0__tar.gz → 2.0.0__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.
Files changed (25) hide show
  1. finalsa_common_models-2.0.0/.gitignore +115 -0
  2. {finalsa_common_models-1.0.0 → finalsa_common_models-2.0.0}/LICENSE.md +1 -1
  3. finalsa_common_models-2.0.0/PKG-INFO +87 -0
  4. finalsa_common_models-2.0.0/README.md +52 -0
  5. {finalsa_common_models-1.0.0 → finalsa_common_models-2.0.0}/finalsa/common/models/__init__.py +2 -3
  6. finalsa_common_models-2.0.0/finalsa/common/models/models/__init__.py +10 -0
  7. finalsa_common_models-2.0.0/finalsa/common/models/models/meta.py +10 -0
  8. finalsa_common_models-2.0.0/finalsa/common/models/models/sqs_response.py +64 -0
  9. finalsa_common_models-2.0.0/pyproject.toml +45 -0
  10. finalsa_common_models-1.0.0/PKG-INFO +0 -23
  11. finalsa_common_models-1.0.0/README.md +0 -0
  12. finalsa_common_models-1.0.0/finalsa/common/models/models/__init__.py +0 -3
  13. finalsa_common_models-1.0.0/finalsa/common/models/models/sqs_message.py +0 -27
  14. finalsa_common_models-1.0.0/finalsa/common/models/models/sqs_response.py +0 -114
  15. finalsa_common_models-1.0.0/finalsa/common/models/py.typed +0 -0
  16. finalsa_common_models-1.0.0/finalsa_common_models.egg-info/PKG-INFO +0 -23
  17. finalsa_common_models-1.0.0/finalsa_common_models.egg-info/SOURCES.txt +0 -17
  18. finalsa_common_models-1.0.0/finalsa_common_models.egg-info/dependency_links.txt +0 -1
  19. finalsa_common_models-1.0.0/finalsa_common_models.egg-info/requires.txt +0 -2
  20. finalsa_common_models-1.0.0/finalsa_common_models.egg-info/top_level.txt +0 -2
  21. finalsa_common_models-1.0.0/finalsa_common_models.egg-info/zip-safe +0 -1
  22. finalsa_common_models-1.0.0/setup.cfg +0 -10
  23. finalsa_common_models-1.0.0/setup.py +0 -77
  24. finalsa_common_models-1.0.0/tests/test_client.py +0 -355
  25. {finalsa_common_models-1.0.0 → finalsa_common_models-2.0.0}/finalsa/common/models/models/functions.py +0 -0
@@ -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) 2021 Luis Diego Jiménez Delgado
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.0
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](#).
@@ -1,14 +1,13 @@
1
1
  from finalsa.common.models.models import (
2
- SqsMessage,
2
+ Meta,
3
3
  SqsReponse,
4
4
  parse_message_attributes,
5
5
  to_message_attributes
6
6
  )
7
7
 
8
- __version__ = "1.0.0"
9
8
 
10
9
  __all__ = [
11
- "SqsMessage",
10
+ "Meta",
12
11
  "SqsReponse",
13
12
  "parse_message_attributes",
14
13
  "to_message_attributes",
@@ -0,0 +1,10 @@
1
+ from .functions import parse_message_attributes, to_message_attributes
2
+ from .sqs_response import SqsReponse
3
+ from .meta import Meta
4
+
5
+ __all__ = [
6
+ "Meta",
7
+ "SqsReponse",
8
+ "parse_message_attributes",
9
+ "to_message_attributes",
10
+ ]
@@ -0,0 +1,10 @@
1
+ from pydantic import BaseModel
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+
6
+ class Meta(BaseModel):
7
+ authorization: Optional[dict] = {}
8
+ timestamp: datetime
9
+ correlation_id: str
10
+ ip: str
@@ -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_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_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_message_attributes(
63
+ response.get('MessageAttributes', {}))
64
+ )
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "finalsa-common-models"
3
+ version = "2.0.0"
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.0
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,3 +0,0 @@
1
- from .functions import parse_message_attributes, to_message_attributes
2
- from .sqs_message import SqsMessage
3
- from .sqs_response import SqsReponse
@@ -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['timestamp']
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
- )
@@ -1,23 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: finalsa-common-models
3
- Version: 1.0.0
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,2 +0,0 @@
1
- boto3>=1.20.3
2
- pydantic>=2.5.2
@@ -1,2 +0,0 @@
1
- finalsa/common/models
2
- finalsa/common/models/models
@@ -1,10 +0,0 @@
1
- [metadata]
2
- description-file = README.md
3
-
4
- [pep8]
5
- max-line-length = 88
6
-
7
- [egg_info]
8
- tag_build =
9
- tag_date = 0
10
-
@@ -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'