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.
Files changed (34) hide show
  1. plexop_infrastructure-1.1.0/MANIFEST.in +22 -0
  2. plexop_infrastructure-1.1.0/PKG-INFO +39 -0
  3. plexop_infrastructure-1.1.0/README.md +22 -0
  4. plexop_infrastructure-1.1.0/azure-pipelines-build-tag.yml +38 -0
  5. plexop_infrastructure-1.1.0/pyproject.toml +47 -0
  6. plexop_infrastructure-1.1.0/setup.cfg +4 -0
  7. plexop_infrastructure-1.1.0/src/debug.py +18 -0
  8. plexop_infrastructure-1.1.0/src/plexop/__init__.py +0 -0
  9. plexop_infrastructure-1.1.0/src/plexop/_version.py +24 -0
  10. plexop_infrastructure-1.1.0/src/plexop/infrastructure/__init__.py +14 -0
  11. plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/DynamoDB.py +67 -0
  12. plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/Firehose.py +17 -0
  13. plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/KMS.py +25 -0
  14. plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/S3.py +41 -0
  15. plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/SNS.py +18 -0
  16. plexop_infrastructure-1.1.0/src/plexop/infrastructure/aws/SQS.py +42 -0
  17. plexop_infrastructure-1.1.0/src/plexop/infrastructure/cache_manager.py +33 -0
  18. plexop_infrastructure-1.1.0/src/plexop/infrastructure/config_provider.py +69 -0
  19. plexop_infrastructure-1.1.0/src/plexop/infrastructure/dict_csv_reader.py +11 -0
  20. plexop_infrastructure-1.1.0/src/plexop/infrastructure/expiringdict.py +157 -0
  21. plexop_infrastructure-1.1.0/src/plexop/infrastructure/hashing.py +14 -0
  22. plexop_infrastructure-1.1.0/src/plexop/infrastructure/http_requester.py +77 -0
  23. plexop_infrastructure-1.1.0/src/plexop/infrastructure/lang_codes.py +197 -0
  24. plexop_infrastructure-1.1.0/src/plexop/infrastructure/log_provider.py +190 -0
  25. plexop_infrastructure-1.1.0/src/plexop/infrastructure/lzstring/__init__.py +428 -0
  26. plexop_infrastructure-1.1.0/src/plexop/infrastructure/models/__init__.py +1 -0
  27. plexop_infrastructure-1.1.0/src/plexop/infrastructure/models/external_service_exception.py +8 -0
  28. plexop_infrastructure-1.1.0/src/plexop/infrastructure/models/write_condition_not_met_exception.py +3 -0
  29. plexop_infrastructure-1.1.0/src/plexop/infrastructure/sleep_iterator.py +14 -0
  30. plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/PKG-INFO +39 -0
  31. plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/SOURCES.txt +32 -0
  32. plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/dependency_links.txt +1 -0
  33. plexop_infrastructure-1.1.0/src/plexop_infrastructure.egg-info/requires.txt +5 -0
  34. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
@@ -0,0 +1,11 @@
1
+ import csv
2
+
3
+
4
+ def read_file(csv_file_path, delimiter='\t'):
5
+ results = []
6
+ with open(csv_file_path) as csv_file:
7
+ reader = csv.DictReader(csv_file, delimiter=delimiter)
8
+ for row in reader:
9
+ results.append(row)
10
+
11
+ return results