plexop-infrastructure 1.1.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.
- plexop_infrastructure-1.1.0/MANIFEST.in +22 -0
- plexop_infrastructure-1.1.0/PKG-INFO +39 -0
- plexop_infrastructure-1.1.0/README.md +22 -0
- plexop_infrastructure-1.1.0/azure-pipelines-build-tag.yml +38 -0
- plexop_infrastructure-1.1.0/pyproject.toml +47 -0
- plexop_infrastructure-1.1.0/setup.cfg +4 -0
- plexop_infrastructure-1.1.0/src/debug.py +18 -0
- plexop_infrastructure-1.1.0/src/plexop/__init__.py +0 -0
- plexop_infrastructure-1.1.0/src/plexop/_version.py +24 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/__init__.py +14 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/DynamoDB.py +67 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/Firehose.py +17 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/KMS.py +25 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/S3.py +41 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/SNS.py +18 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/SQS.py +42 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/cache_manager.py +33 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/config_provider.py +69 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/dict_csv_reader.py +11 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/expiringdict.py +157 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/hashing.py +14 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/http_requester.py +77 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/lang_codes.py +197 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/log_provider.py +190 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/lzstring/__init__.py +428 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/models/__init__.py +1 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/models/external_service_exception.py +8 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/models/write_condition_not_met_exception.py +3 -0
- plexop_infrastructure-1.1.0/src/plexop/infrastructure/sleep_iterator.py +14 -0
- plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/PKG-INFO +39 -0
- plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/SOURCES.txt +32 -0
- plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/dependency_links.txt +1 -0
- plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/requires.txt +5 -0
- plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include pyproject.toml
|
|
3
|
+
include LICENSE
|
|
4
|
+
include MANIFEST.in
|
|
5
|
+
# Include the package code
|
|
6
|
+
recursive-include src *
|
|
7
|
+
|
|
8
|
+
# Explicitly exclude lambda files
|
|
9
|
+
prune lambda_env
|
|
10
|
+
prune layer
|
|
11
|
+
prune layer.zip
|
|
12
|
+
prune lambda_layer.zip
|
|
13
|
+
|
|
14
|
+
# Exclude these files from main directory
|
|
15
|
+
exclude azure-pipelines.yml
|
|
16
|
+
exclude .gitignore
|
|
17
|
+
exclude .gitattributes
|
|
18
|
+
exclude *.pyproj
|
|
19
|
+
exclude *.version
|
|
20
|
+
exclude conf.cfg
|
|
21
|
+
exclude requirements.txt
|
|
22
|
+
exclude *.bat
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plexop-infrastructure
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Plexop infrastructure shared code
|
|
5
|
+
Author: Vlad Stremousov
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Requires-Dist: boto3
|
|
14
|
+
Requires-Dist: python-dateutil
|
|
15
|
+
Requires-Dist: urllib3
|
|
16
|
+
Requires-Dist: pygelf
|
|
17
|
+
|
|
18
|
+
Package with Plexop Python infrastructure
|
|
19
|
+
Include
|
|
20
|
+
* Logger decorator
|
|
21
|
+
* Config decorator
|
|
22
|
+
* Requests decorator
|
|
23
|
+
* Some boto3 stuff decorators
|
|
24
|
+
|
|
25
|
+
Install from http://static.plexop.com/packages/python/plexop-infrastructure-latest.tar.gz
|
|
26
|
+
|
|
27
|
+
in order to upgrade dependencies:
|
|
28
|
+
cd C:\Projects\python-lambda-infrastructure-layer
|
|
29
|
+
pip install --upgrade -r requirements.txt --target=C:\Projects\python-lambda-infrastructure-layer\env\Lib\site-packages
|
|
30
|
+
|
|
31
|
+
# Installation
|
|
32
|
+
* The layer can be used as pip-installable package and as a lambda layer.
|
|
33
|
+
* Packages are located in `s3://static-plexop/packages/python/plexop-infrastructure`. Open a given version and copy it's URL.
|
|
34
|
+
* It can be installed via e.g. `pip install http://static.plexop.com/packages/python/plexop-infrastructure-0.2.8.tar.gz`
|
|
35
|
+
* Latest version is always here: http://static.plexop.com/packages/python/plexop-infrastructure-latest.tar.gz
|
|
36
|
+
|
|
37
|
+
# Deployment
|
|
38
|
+
* Each commit to this repo will create a new version (both on s3 & layer) with version name <branch>-<commit>-<date>
|
|
39
|
+
* To deploy a given commit to PROD, create a new branch based off this commit and name the branch with some semantic version and add a `-release` suffix (e.g. `1.3-release`)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Package with Plexop Python infrastructure
|
|
2
|
+
Include
|
|
3
|
+
* Logger decorator
|
|
4
|
+
* Config decorator
|
|
5
|
+
* Requests decorator
|
|
6
|
+
* Some boto3 stuff decorators
|
|
7
|
+
|
|
8
|
+
Install from http://static.plexop.com/packages/python/plexop-infrastructure-latest.tar.gz
|
|
9
|
+
|
|
10
|
+
in order to upgrade dependencies:
|
|
11
|
+
cd C:\Projects\python-lambda-infrastructure-layer
|
|
12
|
+
pip install --upgrade -r requirements.txt --target=C:\Projects\python-lambda-infrastructure-layer\env\Lib\site-packages
|
|
13
|
+
|
|
14
|
+
# Installation
|
|
15
|
+
* The layer can be used as pip-installable package and as a lambda layer.
|
|
16
|
+
* Packages are located in `s3://static-plexop/packages/python/plexop-infrastructure`. Open a given version and copy it's URL.
|
|
17
|
+
* It can be installed via e.g. `pip install http://static.plexop.com/packages/python/plexop-infrastructure-0.2.8.tar.gz`
|
|
18
|
+
* Latest version is always here: http://static.plexop.com/packages/python/plexop-infrastructure-latest.tar.gz
|
|
19
|
+
|
|
20
|
+
# Deployment
|
|
21
|
+
* Each commit to this repo will create a new version (both on s3 & layer) with version name <branch>-<commit>-<date>
|
|
22
|
+
* To deploy a given commit to PROD, create a new branch based off this commit and name the branch with some semantic version and add a `-release` suffix (e.g. `1.3-release`)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
trigger:
|
|
2
|
+
branches:
|
|
3
|
+
include:
|
|
4
|
+
- master # Run only on master commits
|
|
5
|
+
tags:
|
|
6
|
+
exclude:
|
|
7
|
+
- "*" # Never run on tags
|
|
8
|
+
|
|
9
|
+
pool:
|
|
10
|
+
name: default-linux
|
|
11
|
+
|
|
12
|
+
stages:
|
|
13
|
+
- stage: SemanticVersioning
|
|
14
|
+
displayName: "Semantic Versioning (Commitizen)"
|
|
15
|
+
jobs:
|
|
16
|
+
- job: BumpVersion
|
|
17
|
+
displayName: "Bump version & create git tag"
|
|
18
|
+
container:
|
|
19
|
+
image: python:3.13
|
|
20
|
+
options: --user root
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- checkout: self
|
|
24
|
+
fetchDepth: 0
|
|
25
|
+
persistCredentials: true
|
|
26
|
+
|
|
27
|
+
- script: |
|
|
28
|
+
pip install --upgrade pip
|
|
29
|
+
pip install commitizen
|
|
30
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
31
|
+
git config user.email "$(Build.RequestedForEmail)"
|
|
32
|
+
git config user.name "$(Build.RequestedFor)"
|
|
33
|
+
git fetch origin
|
|
34
|
+
git checkout master
|
|
35
|
+
git reset --hard origin/master
|
|
36
|
+
echo "Running Commitizen bump..."
|
|
37
|
+
cz bump --yes || echo "No new release detected."
|
|
38
|
+
displayName: "Semantic Versioning (Commitizen)"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel", "setuptools-scm"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "plexop-infrastructure" # PyPI distribution name (hyphens OK)
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Plexop infrastructure shared code"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "Vlad Stremousov" }]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"requests",
|
|
20
|
+
"boto3",
|
|
21
|
+
"python-dateutil",
|
|
22
|
+
"urllib3",
|
|
23
|
+
"pygelf",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
package-dir = {"" = "src"}
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
31
|
+
include = ["plexop*"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools_scm]
|
|
34
|
+
write_to = "src/plexop/_version.py"
|
|
35
|
+
|
|
36
|
+
# Commitizen: parse conventional commits and bump version
|
|
37
|
+
[tool.commitizen]
|
|
38
|
+
name = "cz_conventional_commits"
|
|
39
|
+
version = "1.1.0"
|
|
40
|
+
tag_format = "v$version"
|
|
41
|
+
version_files = ["pyproject.toml"]
|
|
42
|
+
post_bump_hooks = [
|
|
43
|
+
"git push",
|
|
44
|
+
"git push --tags"
|
|
45
|
+
]
|
|
46
|
+
update_changelog_on_bump = false
|
|
47
|
+
bump_message = "bump: version $current_version → $new_version [skip ci]"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from plexop.infrastructure import logger, config
|
|
2
|
+
from plexop.infrastructure.http_requester import HttpRequester
|
|
3
|
+
|
|
4
|
+
logger.debug('test', obj={'mobile_number': '+972-55-55-55-55', 'email': 'asdfsDf@gmaiL.com', 'test': 'test',
|
|
5
|
+
'original_querystring': '?asdfd=213&mobile=&phone=234&email=asdfsdf@gmail.com'})
|
|
6
|
+
|
|
7
|
+
#r = HttpRequester()
|
|
8
|
+
#result = r.execute('http://5aklie.free.bg', read_as_json=False, use_cache=True, raw_response=1)
|
|
9
|
+
#result = r.execute('http://5aklie.free.bg', read_as_json=False, use_cache=True, raw_response=1)
|
|
10
|
+
|
|
11
|
+
# from plexop.infrastructure.aws.SNS import SNS
|
|
12
|
+
# sns = SNS(profile_name='dev')
|
|
13
|
+
# sns.publish('arn:aws:sns:us-east-1:872540588091:sophie-test', 'test')
|
|
14
|
+
#
|
|
15
|
+
# from plexop.infrastructure.aws.SQS import SQS
|
|
16
|
+
# sqs = SQS(region=None, profile_name='dev')
|
|
17
|
+
# sqs.push_message('https://sqs.us-east-1.amazonaws.com/872540588091/hello-world', 'test')
|
|
18
|
+
# sqs.push_messages('https://sqs.us-east-1.amazonaws.com/872540588091/hello-world', ['test1', 'test2'])
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '1.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from plexop.infrastructure.config_provider import get_config
|
|
2
|
+
from plexop.infrastructure.log_provider import Logger
|
|
3
|
+
|
|
4
|
+
if 'config' not in globals():
|
|
5
|
+
config = get_config()
|
|
6
|
+
|
|
7
|
+
if 'logger' not in globals():
|
|
8
|
+
Logger(config, 'quart')
|
|
9
|
+
Logger(config, 'flask')
|
|
10
|
+
Logger(config, 'boto3')
|
|
11
|
+
logger = Logger(config)
|
|
12
|
+
|
|
13
|
+
def get_logger(logger_name):
|
|
14
|
+
return Logger(config, logger_name)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
from botocore.exceptions import ClientError
|
|
3
|
+
from plexop.infrastructure.models.write_condition_not_met_exception import WriteConditionNotMetError
|
|
4
|
+
from plexop.infrastructure.sleep_iterator import sleep_iterator
|
|
5
|
+
from plexop.infrastructure import config, logger
|
|
6
|
+
import time
|
|
7
|
+
from functools import wraps
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def decorate_with_try(fn):
|
|
11
|
+
@wraps(fn)
|
|
12
|
+
def surround(*args, **kwargs):
|
|
13
|
+
sleeper = sleep_iterator()
|
|
14
|
+
start_time = time.time()
|
|
15
|
+
while next(sleeper):
|
|
16
|
+
try:
|
|
17
|
+
logger.debug("try to execute dynamo operation", kwargs=kwargs, fn=str(fn))
|
|
18
|
+
result = fn(*args, **kwargs)
|
|
19
|
+
logger.debug("dynamo operation executed successfully", kwargs=kwargs, fn=str(fn), result=result)
|
|
20
|
+
return result
|
|
21
|
+
except ClientError as ex:
|
|
22
|
+
elapsed_time = time.time() - start_time
|
|
23
|
+
error_code = ex.response['Error']['Code']
|
|
24
|
+
if error_code == u'ConditionalCheckFailedException':
|
|
25
|
+
raise WriteConditionNotMetError()
|
|
26
|
+
logger.exception(ex, function_name=fn.__name__, elapsed_time=elapsed_time, args=str(args),
|
|
27
|
+
kwargs=str(kwargs), error_code=error_code)
|
|
28
|
+
if error_code == u'ProvisionedThroughputExceededException':
|
|
29
|
+
continue
|
|
30
|
+
raise
|
|
31
|
+
|
|
32
|
+
return surround
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DynamoDB:
|
|
36
|
+
def __init__(self, region=None, profile_name=None):
|
|
37
|
+
region = region or config.get('dynamodb', 'region', fallback=None)
|
|
38
|
+
profile_name = profile_name or config.get('dynamodb', 'profile_name', fallback=None)
|
|
39
|
+
self.client = boto3.session.Session(profile_name=profile_name, region_name=region).client('dynamodb')
|
|
40
|
+
|
|
41
|
+
@decorate_with_try
|
|
42
|
+
def get_item(self, *args, **kwargs):
|
|
43
|
+
response = self.client.get_item(**kwargs)
|
|
44
|
+
if 'Item' not in response:
|
|
45
|
+
return None
|
|
46
|
+
result = dict((key, list(value.values())[0]) for key, value in response['Item'].items())
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
@decorate_with_try
|
|
50
|
+
def query(self, *args, **kwargs):
|
|
51
|
+
response = self.client.query(**kwargs)
|
|
52
|
+
return response
|
|
53
|
+
|
|
54
|
+
@decorate_with_try
|
|
55
|
+
def put_item(self, *args, **kwargs):
|
|
56
|
+
result = self.client.put_item(**kwargs)
|
|
57
|
+
return result.get('Attributes')
|
|
58
|
+
|
|
59
|
+
@decorate_with_try
|
|
60
|
+
def update_item(self, *args, **kwargs):
|
|
61
|
+
result = self.client.update_item(**kwargs)
|
|
62
|
+
return result.get('Attributes')
|
|
63
|
+
|
|
64
|
+
@decorate_with_try
|
|
65
|
+
def delete_item(self, *args, **kwargs):
|
|
66
|
+
query_result = self.client.delete_item(**kwargs)
|
|
67
|
+
result = dict((key, list(value.values())[0]) for key, value in query_result['Attributes'].items())
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
from plexop.infrastructure import config, logger
|
|
5
|
+
|
|
6
|
+
class Firehose:
|
|
7
|
+
def __init__(self, region=None, profile_name=None):
|
|
8
|
+
region = region or config.get('firehose', 'region', fallback=None)
|
|
9
|
+
profile_name = profile_name or config.get('firehose', 'profile_name', fallback=None)
|
|
10
|
+
self.client = boto3.session.Session(profile_name=profile_name, region_name=region).client('firehose')
|
|
11
|
+
|
|
12
|
+
def put_record(self, data, stream_name=None):
|
|
13
|
+
stream_name = stream_name or config.get('firehose', 'stream_name')
|
|
14
|
+
logger.debug("sending data to fireshose stream", stream_name=stream_name, data=data)
|
|
15
|
+
result = self.client.put_record(DeliveryStreamName=stream_name, Record={'Data': data})
|
|
16
|
+
logger.debug("data sent to firehose successfully", result=result)
|
|
17
|
+
return result
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
from plexop.infrastructure import config, logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KMS:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
kms_region = config.get('kms', 'region')
|
|
10
|
+
self.key_id = config.get('kms', 'key_id')
|
|
11
|
+
if self.key_id:
|
|
12
|
+
self.client = boto3.client('kms', region_name=kms_region)
|
|
13
|
+
|
|
14
|
+
def encrypt(self, message):
|
|
15
|
+
if not self.key_id:
|
|
16
|
+
return message
|
|
17
|
+
result = self.client.encrypt(KeyId=self.key_id, Plaintext=message)
|
|
18
|
+
return base64.b64encode(result['CiphertextBlob']).decode("utf-8")
|
|
19
|
+
|
|
20
|
+
def decrypt(self, message):
|
|
21
|
+
if not self.key_id:
|
|
22
|
+
return message
|
|
23
|
+
decrypted_message = self.client.decrypt(CiphertextBlob=base64.b64decode(message))
|
|
24
|
+
result = decrypted_message['Plaintext']
|
|
25
|
+
return result
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import json
|
|
3
|
+
from plexop.infrastructure import config, logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class S3:
|
|
7
|
+
def __init__(self, region=None, profile_name=None):
|
|
8
|
+
region = region or config.get('s3', 'region', fallback=None)
|
|
9
|
+
profile_name = profile_name or config.get('s3', 'profile_name', fallback=None)
|
|
10
|
+
self.client = boto3.session.Session(profile_name=profile_name, region_name=region).client('s3')
|
|
11
|
+
|
|
12
|
+
def read(self, bucket_name, key, read_as_json=True, suppress_404_exception=False, is_binary=False):
|
|
13
|
+
try:
|
|
14
|
+
logger.debug("try to read from S3", bucket_name=bucket_name, key=key)
|
|
15
|
+
file = self.client.get_object(Bucket=bucket_name, Key=key)
|
|
16
|
+
file_content = file['Body'].read()
|
|
17
|
+
if is_binary:
|
|
18
|
+
logger.debug("binary data loaded from S3")
|
|
19
|
+
return file_content
|
|
20
|
+
file_content = file_content.decode('utf-8')
|
|
21
|
+
if read_as_json:
|
|
22
|
+
json_result = json.loads(file_content)
|
|
23
|
+
logger.debug("json content loaded from S3", result=json_result)
|
|
24
|
+
return json_result
|
|
25
|
+
else:
|
|
26
|
+
logger.debug("content loaded from S3", result=file_content)
|
|
27
|
+
return file_content
|
|
28
|
+
except Exception as ex:
|
|
29
|
+
if hasattr(ex, 'response') and ex.response['Error']['Code'] == 'NoSuchKey' and suppress_404_exception:
|
|
30
|
+
logger.info("File not found", bucket_name=bucket_name, key=key)
|
|
31
|
+
return
|
|
32
|
+
logger.exception(ex)
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
def save(self, bucket_name, key, body):
|
|
36
|
+
logger.debug("try to put object to S3", bucket_name=bucket_name, key=key, body=body)
|
|
37
|
+
if isinstance(body, dict):
|
|
38
|
+
body = json.dumps(body)
|
|
39
|
+
result = self.client.put_object(Bucket=bucket_name, Key=key, Body=body)
|
|
40
|
+
logger.debug("content saved", result=result)
|
|
41
|
+
return result
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import json
|
|
3
|
+
from plexop.infrastructure import config, logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SNS:
|
|
7
|
+
def __init__(self, region=None, profile_name=None):
|
|
8
|
+
region = region or config.get('sns', 'region', fallback=None)
|
|
9
|
+
profile_name = profile_name or config.get('sqs', 'profile_name', fallback=None)
|
|
10
|
+
self.client = boto3.session.Session(profile_name=profile_name, region_name=region).client('sns')
|
|
11
|
+
|
|
12
|
+
def publish(self, TopicArn, Message, **kwargs):
|
|
13
|
+
logger.debug("sending message to sns", topic=TopicArn, message_to_publish=Message, **kwargs)
|
|
14
|
+
if not isinstance(Message, str):
|
|
15
|
+
Message = json.dumps(Message)
|
|
16
|
+
publication_result = self.client.publish(TopicArn=TopicArn, Message=Message, **kwargs)
|
|
17
|
+
logger.debug("message sent to sns", result=publication_result)
|
|
18
|
+
return publication_result
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import json
|
|
3
|
+
import uuid
|
|
4
|
+
from plexop.infrastructure import config, logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SQS:
|
|
8
|
+
def __init__(self, region=None, profile_name=None):
|
|
9
|
+
region = region or config.get('sqs', 'region', fallback=None)
|
|
10
|
+
profile_name = profile_name or config.get('sqs', 'profile_name', fallback=None)
|
|
11
|
+
self.client = boto3.session.Session(profile_name=profile_name, region_name=region).client('sqs')
|
|
12
|
+
|
|
13
|
+
def push_message(self, QueueUrl, MessageBody, **kwargs):
|
|
14
|
+
logger.debug("Try to send message to queue", queue_url=QueueUrl, message_body=MessageBody, **kwargs)
|
|
15
|
+
if isinstance(MessageBody, dict):
|
|
16
|
+
MessageBody = json.dumps(MessageBody)
|
|
17
|
+
receipt = self.client.send_message(QueueUrl=QueueUrl, MessageBody=MessageBody, **kwargs)
|
|
18
|
+
logger.debug("Message sent to SQS", receipt=receipt)
|
|
19
|
+
return receipt
|
|
20
|
+
|
|
21
|
+
def push_messages(self, QueueUrl, MessageBodies, **kwargs):
|
|
22
|
+
def build_message_for_bulk(message):
|
|
23
|
+
return {
|
|
24
|
+
'Id': str(uuid.uuid1()),
|
|
25
|
+
'MessageBody': message if isinstance(message, str) else json.dumps(message),
|
|
26
|
+
}
|
|
27
|
+
if not hasattr(MessageBodies, '__iter__'):
|
|
28
|
+
raise Exception("Bodies argument for bulk messages SQS push is not iterable")
|
|
29
|
+
if not len(MessageBodies):
|
|
30
|
+
logger.warn("Push bulk messages call failed. Bodies array length is 0")
|
|
31
|
+
return
|
|
32
|
+
logger.debug('Try to push messages to SQS', queue_url=QueueUrl, count=len(MessageBodies))
|
|
33
|
+
messages = [build_message_for_bulk(m) for m in MessageBodies]
|
|
34
|
+
result = self.client.send_message_batch(QueueUrl=QueueUrl, Entries=messages, **kwargs)
|
|
35
|
+
logger.debug("Messages bulk sent successfully to SQS", queue_url=QueueUrl, count=len(MessageBodies), response=result)
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def delete_message(self, QueueUrl, ReceiptHandle, **kwargs):
|
|
39
|
+
logger.debug("Try to delete message", queue_url=QueueUrl, receipt_handle=ReceiptHandle)
|
|
40
|
+
result = self.client.delete_message(QueueUrl=QueueUrl, ReceiptHandle=ReceiptHandle, **kwargs)
|
|
41
|
+
logger.debug("Message deleted successfully", queue_url=QueueUrl, receipt_handle=ReceiptHandle, response=result)
|
|
42
|
+
return result
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
import json
|
|
3
|
+
from plexop.infrastructure import config, logger
|
|
4
|
+
from plexop.infrastructure.expiringdict import ExpiringDict
|
|
5
|
+
|
|
6
|
+
cache_ttl_seconds = config.getint('settings', 'cache_ttl_seconds', fallback=600)
|
|
7
|
+
cache_max_len = config.getint('settings', 'cache_max_len', fallback=1000)
|
|
8
|
+
|
|
9
|
+
_memory = ExpiringDict(max_len=cache_max_len, max_age_seconds=cache_ttl_seconds)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def cache(fn):
|
|
13
|
+
@wraps(fn)
|
|
14
|
+
def surround(self, url, *args, **kwargs):
|
|
15
|
+
|
|
16
|
+
if kwargs.get('method', 'GET').upper() != 'GET' or not kwargs.get('use_cache', False):
|
|
17
|
+
return fn(self, url, *args, **kwargs)
|
|
18
|
+
|
|
19
|
+
cache_url = url
|
|
20
|
+
if 'params' in kwargs:
|
|
21
|
+
cache_url += json.dumps(kwargs['params'])
|
|
22
|
+
if 'headers' in kwargs:
|
|
23
|
+
cache_url += json.dumps(kwargs['headers'])
|
|
24
|
+
result = _memory.get(cache_url)
|
|
25
|
+
if not result:
|
|
26
|
+
result = fn(self, url, *args, **kwargs)
|
|
27
|
+
str_result = str(result)
|
|
28
|
+
if 'error' not in str_result and 'timed' not in str_result:
|
|
29
|
+
_memory[cache_url] = result
|
|
30
|
+
else:
|
|
31
|
+
logger.debug("result found in cache", key=cache_url)
|
|
32
|
+
return result
|
|
33
|
+
return surround
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import os
|
|
3
|
+
from base64 import b64decode
|
|
4
|
+
|
|
5
|
+
_config: configparser.ConfigParser = None
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def read_config_from(path):
|
|
9
|
+
config = configparser.ConfigParser(allow_no_value=True)
|
|
10
|
+
try:
|
|
11
|
+
config.read(path)
|
|
12
|
+
return config
|
|
13
|
+
except IOError:
|
|
14
|
+
pass
|
|
15
|
+
return config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def read_config_from_env_variables():
|
|
19
|
+
for key, value in os.environ.items():
|
|
20
|
+
section_name, _, param_name = key.partition('__')
|
|
21
|
+
if param_name and section_name:
|
|
22
|
+
if not _config.has_section(section_name.lower()):
|
|
23
|
+
_config.add_section(section_name.lower())
|
|
24
|
+
_config.set(section_name.lower(), param_name.lower(), value)
|
|
25
|
+
decrypt_env_variables()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def decrypt_env_variables():
|
|
29
|
+
encrypted_fields = _config.get('settings', 'encrypted_fields', fallback=None)
|
|
30
|
+
if encrypted_fields:
|
|
31
|
+
encrypted_fields = encrypted_fields.split(',')
|
|
32
|
+
import boto3
|
|
33
|
+
kms_client = boto3.client('kms')
|
|
34
|
+
for k in encrypted_fields:
|
|
35
|
+
section, _, param_name = k.lower().partition('__')
|
|
36
|
+
enc_value = _config.get(section, param_name)
|
|
37
|
+
decryption_result = kms_client.decrypt(CiphertextBlob=b64decode(enc_value), EncryptionContext={
|
|
38
|
+
'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']})
|
|
39
|
+
dec_value = decryption_result['Plaintext'].decode('utf-8')
|
|
40
|
+
_config.set(section, param_name, dec_value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_configs_from_aws_secrets_manager():
|
|
44
|
+
secret_names = _config.get('settings', 'aws_secrets_names', fallback=None)
|
|
45
|
+
if secret_names:
|
|
46
|
+
import boto3
|
|
47
|
+
import json
|
|
48
|
+
aws_region = _config.get('settings', 'aws_secrets_region', fallback=None)
|
|
49
|
+
if not aws_region:
|
|
50
|
+
boto3.setup_default_session()
|
|
51
|
+
aws_region = boto3.DEFAULT_SESSION.region_name
|
|
52
|
+
client = boto3.client('secretsmanager', region_name=aws_region)
|
|
53
|
+
for secret_name in secret_names.split(','):
|
|
54
|
+
response = client.get_secret_value(SecretId=secret_name)
|
|
55
|
+
result = json.loads(response['SecretString'])
|
|
56
|
+
for complex_key, value in result.items():
|
|
57
|
+
section, key = complex_key.split('__')
|
|
58
|
+
if not _config.has_section(section):
|
|
59
|
+
_config.add_section(section)
|
|
60
|
+
_config.set(section, key, value)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_config():
|
|
64
|
+
global _config
|
|
65
|
+
if not _config:
|
|
66
|
+
_config = read_config_from(os.getenv('CONFIG_FILE_PATH', 'conf.cfg'))
|
|
67
|
+
read_config_from_env_variables()
|
|
68
|
+
load_configs_from_aws_secrets_manager()
|
|
69
|
+
return _config
|