├── version.txt ├── heybooster ├── __init__.py ├── exception │ ├── __init__.py │ └── exception_catcher.py ├── helpers │ ├── __init__.py │ ├── email │ │ ├── __init__.py │ │ ├── statics │ │ │ └── __init__.py │ │ ├── utils │ │ │ └── __init__.py │ │ └── sendpulse.py │ ├── hubspot │ │ ├── __init__.py │ │ └── contacts.py │ └── database │ │ ├── __init__.py │ │ ├── postgre.py │ │ ├── opensearch.py │ │ └── mongodb.py └── logger │ ├── __init__.py │ └── cloudwatch │ ├── __init__.py │ └── cloudwatch_logger.py ├── MANIFEST.in ├── pyproject.toml ├── publish.sh ├── version_update.py ├── examples └── mongodb_example.py ├── setup.py ├── LICENSE ├── README.md ├── .gitignore └── CODE_OF_CONDUCT.md /version.txt: -------------------------------------------------------------------------------- 1 | 0.0.41 -------------------------------------------------------------------------------- /heybooster/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/exception/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/logger/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/helpers/email/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/helpers/hubspot/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/helpers/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heybooster/logger/cloudwatch/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.toml 2 | include *.py 3 | include version.txt 4 | include LICENSE 5 | include README.rst 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /heybooster/helpers/email/statics/__init__.py: -------------------------------------------------------------------------------- 1 | BASE_URL = 'https://api.sendpulse.com/' 2 | ACCESS_URL_PATH = 'oauth/access_token' 3 | SMTP_URL_PATH = 'smtp/emails' 4 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | git pull 2 | rm -rf dist 3 | python3 version_update.py 4 | python3 -m pip install --upgrade build 5 | python3 -m build 6 | python3 -m pip install --upgrade twine 7 | python3 -m twine upload --repository pypi dist/* 8 | git add . 9 | git commit -m "New Version Publish" 10 | git push -f 11 | -------------------------------------------------------------------------------- /version_update.py: -------------------------------------------------------------------------------- 1 | """ Update Version Script """ 2 | with open("version.txt", "r") as version_file: 3 | version = version_file.read() 4 | version_numbers = version.split(".") 5 | 6 | version_numbers[-1] = str(int(version_numbers[-1]) + 1) 7 | new_version = ".".join(version_numbers) 8 | 9 | with open("version.txt", "w") as version_file: 10 | version_file.write(new_version) 11 | -------------------------------------------------------------------------------- /heybooster/helpers/email/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class HTTPMethods(Enum): 4 | delete = 0 5 | get = 1 6 | post = 2 7 | put = 3 8 | 9 | 10 | class AuthenticationError(Exception): 11 | def __str__(self) -> str: 12 | return 'Your credentials invalid. Check your credentials.' 13 | 14 | 15 | class SendProcessFailureError(Exception): 16 | def __str__(self) -> str: 17 | return 'Email sending procedure failed.' 18 | -------------------------------------------------------------------------------- /examples/mongodb_example.py: -------------------------------------------------------------------------------- 1 | from heybooster.helpers.database.mongodb import MongoDBHelper 2 | 3 | NAME = "database_name" 4 | URI = "database_uri" 5 | 6 | """ 7 | Usage 'with' 8 | """ 9 | with MongoDBHelper(uri=URI, database=NAME) as db: 10 | result = db.find_one('test_collection', query={'email': 'test@email.com'}) 11 | result = db.find('test_collection', query={'email': 'test@email.com'}) 12 | db.insert('test_collection', query={'email': 'test@email.com'}) 13 | db.insert('test_collection', query={'email': 'test@email.com'}) 14 | db.find_and_modify('test_collection', query={'email': 'test@email.com'}, update={"$set": 'test2@gmail.com'}) 15 | 16 | """ 17 | Usage for connection manual closing 18 | """ 19 | db = MongoDBHelper(uri=URI, database=NAME) 20 | result = db.find_one('test_collection', query={'email': 'test@email.com'}) 21 | 22 | db.close() 23 | print(result) 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | with open("version.txt", "r") as version_file: 7 | version = version_file.read() 8 | 9 | setuptools.setup( 10 | name="heybooster-toolkit", 11 | version=version, 12 | author="Heybooster", 13 | author_email="hey@heybooster.ai", 14 | description="Heybooster Toolkit", 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | packages=setuptools.find_packages(), 18 | url="https://github.com/hey-booster/heybooster-toolkit", 19 | project_urls={ 20 | "Bug Tracker": "https://github.com/hey-booster/heybooster-toolkit/issues", 21 | }, 22 | classifiers=[ 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | ], 27 | install_requires=[ 28 | 'pymongo==3.10.1', 29 | 'requests', 30 | 'raven==6.10.0', 31 | 'boto3==1.28.15' 32 | ], 33 | python_requires=">=3.6", 34 | ) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 heybooster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Heybooster MongoDB Helper 2 | 3 | 4 | **Installation:** 5 | ```shell 6 | pip3 install heybooster_toolkit 7 | ``` 8 | 9 | --- 10 | 11 | **Example:** 12 | ```python 13 | from heybooster.helpers.database.mongodb import MongoDBHelper 14 | 15 | 16 | NAME = "database_name" 17 | URI = "database_uri" 18 | 19 | with MongoDBHelper(uri=URI, database=NAME) as db: 20 | result = db.find_one('test_collection', query={'email': 'test@email.com'}) 21 | result = db.find('test_collection', query={'email': 'test@email.com'}) 22 | db.insert('test_collection', query={'email': 'test@email.com'}) 23 | db.insert('test_collection', query={'email': 'test@email.com'}) 24 | db.find_and_modify('test_collection', query={'email': 'test@email.com'}, update={"$set": 'test2@gmail.com'}) 25 | 26 | ``` 27 | 28 | or 29 | 30 | ```python 31 | from heybooster.helpers.database.mongodb import MongoDBHelper 32 | 33 | 34 | NAME = "database_name" 35 | URI = "database_uri" 36 | 37 | db = MongoDBHelper(uri=URI, database=NAME) 38 | result = db.find_one('test_collection', query={'email': 'test@email.com'}) 39 | 40 | db.close() 41 | print(result) 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | heybooster_toolkit.egg-info/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | .idea 133 | .idea/ 134 | -------------------------------------------------------------------------------- /heybooster/exception/exception_catcher.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import asyncio 3 | import logging 4 | 5 | from raven import Client 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class HeyboosterException: 11 | """ Heybooster Exception Class """ 12 | 13 | def __init__(self, sentry_dns=None): 14 | """ Init Function """ 15 | self.dns = sentry_dns 16 | 17 | @staticmethod 18 | def __post_error_message(url: str, message: str, extra: object): 19 | """ 20 | This function send post request of error message and extra data 21 | """ 22 | try: 23 | body = {"error": message, "extra": extra} 24 | requests.post( 25 | url=url, 26 | json=body 27 | ) 28 | except Exception as e: 29 | logger.error(e) 30 | 31 | def exception_catcher(self, **kwargs): 32 | """ Exception Decarator """ 33 | default = kwargs.get('default') 34 | default_callback = kwargs.get('callback') 35 | error_callback = kwargs.get('error_callback') 36 | post_endpoint = kwargs.get('post_endpoint') 37 | extra_data = kwargs.get('extra') 38 | 39 | def wrapper(func): 40 | def run_func(*args, **kwargs): 41 | try: 42 | return func(*args, **kwargs) 43 | except Exception as e: 44 | err = " Path : {path} \n" \ 45 | " Function : {func} \n" \ 46 | " Error: {error}".format( 47 | path=str(func.__code__.co_filename), 48 | func=func.__name__, 49 | error=e 50 | ) 51 | 52 | if self.dns: 53 | sentry = Client(self.dns) 54 | sentry.tags_context({ 55 | 'function_name': str(func.__name__), 56 | 'related_file_name': str(func.__code__.co_filename), 57 | "annotations": str(func.__annotations__) 58 | }) 59 | sentry.captureException() 60 | 61 | if post_endpoint and isinstance(post_endpoint, str): 62 | loop = asyncio.get_event_loop() 63 | loop.run_until_complete( 64 | self.__post_error_message(url=post_endpoint, message=str(e), extra=extra_data) 65 | ) 66 | loop.close() 67 | 68 | if error_callback is not None: 69 | error_callback(e) 70 | 71 | logger.error(err) 72 | 73 | if default_callback is not None: 74 | return default_callback(default) 75 | 76 | return default 77 | 78 | return run_func 79 | 80 | return wrapper 81 | -------------------------------------------------------------------------------- /heybooster/logger/cloudwatch/cloudwatch_logger.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import boto3 4 | import time 5 | import logging 6 | import botocore.errorfactory as boto_error 7 | 8 | logging.basicConfig(level=logging.INFO) 9 | 10 | 11 | class CloudWatchLogger: 12 | def __init__(self, log_group_name: str, stream_name: str = '', region_name='eu-central-1'): 13 | self.logger = logging.getLogger('CloudWatchLogger') 14 | 15 | self.log_group_name = log_group_name 16 | self.log_stream_name = stream_name 17 | self.region_name = region_name 18 | self.log_stream_created = False 19 | 20 | self.__set_client() 21 | self.__create_log_stream() 22 | 23 | def create_new_stream(self, name: str): 24 | """ 25 | This function create new stream on cloudwatch logs 26 | Args: 27 | name: 28 | 29 | Returns: None 30 | 31 | """ 32 | if self.log_stream_name == name: 33 | self.logger.info('Stream Name Already Exists') 34 | 35 | return 36 | 37 | self.log_stream_name = name 38 | self.__create_log_stream() 39 | 40 | def __set_client(self): 41 | """ 42 | Connect AWS with boto3 library 43 | Returns: 44 | 45 | """ 46 | self.logs_client = boto3.client('logs', region_name=self.region_name) 47 | 48 | def __create_log_stream(self): 49 | """ 50 | Call create log stream api on boto3 before control log stream name 51 | Returns: None 52 | 53 | """ 54 | if not self.log_stream_name or not isinstance(self.log_stream_name, str): 55 | self.logger.info('Log Saver Not Running Because Stream Name Invalid') 56 | 57 | return 58 | 59 | try: 60 | self.logs_client.create_log_stream( 61 | logGroupName=self.log_group_name, 62 | logStreamName=self.log_stream_name 63 | ) 64 | except boto_error.ClientError: 65 | pass 66 | except Exception as error: 67 | self.logger.error(error) 68 | 69 | return 70 | 71 | self.log_stream_created = True 72 | 73 | def info(self, message: object): 74 | self.__put_events( 75 | message=f'INFO: {message}' 76 | ) 77 | 78 | def warning(self, message: object): 79 | self.__put_events( 80 | message=f'WARNING: {message}', 81 | _type='warning' 82 | ) 83 | 84 | def error(self, message: object): 85 | self.__put_events( 86 | message=f'ERROR: {message}', 87 | _type='error' 88 | 89 | ) 90 | 91 | def __put_events(self, message: str, _type: str = "info"): 92 | """ 93 | Put Message to Cloudwatch Events and Write Logs 94 | Args: 95 | message: 96 | _type: 97 | 98 | Returns: None 99 | 100 | """ 101 | try: 102 | if message and self.log_stream_name: 103 | self.logs_client.put_log_events( 104 | logGroupName=self.log_group_name, 105 | logStreamName=self.log_stream_name, 106 | logEvents=[ 107 | { 108 | 'timestamp': int(round(time.time() * 1000)), 109 | 'message': message 110 | } 111 | ] 112 | ) 113 | getattr(self.logger, _type)(message) 114 | except Exception as e: 115 | self.logger.error(f'Put Events Error -> {e}') 116 | -------------------------------------------------------------------------------- /heybooster/helpers/database/postgre.py: -------------------------------------------------------------------------------- 1 | """Database Helper""" 2 | import os 3 | import sys 4 | import logging 5 | import psycopg2 6 | import psycopg2.extras 7 | import psycopg2.extensions as psy_ext 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class DBHelper: 13 | """ 14 | This class context manager to db process 15 | """ 16 | 17 | def __init__(self, **kwargs: dict): 18 | """ 19 | Init Function 20 | :param user: required 21 | :param password: required 22 | :param host: required 23 | :param database: reuired 24 | :param port: optional default 5432 25 | """ 26 | try: 27 | 28 | self._connection = psycopg2.connect( 29 | user=kwargs.get("user"), 30 | password=kwargs.get("password"), 31 | host=kwargs.get("host"), 32 | database=kwargs.get("database"), 33 | port=kwargs.get("port", 5432) 34 | ) 35 | self._cursor = self._connection.cursor() 36 | self._connection.autocommit = kwargs.pop("auto_commit", True) 37 | self.last_execute = False 38 | 39 | except psycopg2.OperationalError as e: 40 | raise Exception("*Operational* Error: {}".format(e)) 41 | 42 | except Exception as e: 43 | raise Exception("*Exception* Error: {}".format(e)) 44 | 45 | def return_as_dict(self): 46 | """ 47 | This Function Set Cursor Factory RealDictCursor 48 | :return: 49 | """ 50 | self._cursor.close() 51 | self._cursor = self._connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) 52 | 53 | return self 54 | 55 | def cursor(self) -> psy_ext.cursor: 56 | """ 57 | This function return connection 58 | :return: Postgre Connection 59 | """ 60 | return self._cursor 61 | 62 | def connection(self) -> psy_ext.connection: 63 | """ 64 | This function return connection 65 | :return: Postgre Connection 66 | """ 67 | return self._connection 68 | 69 | def __enter__(self): 70 | """ 71 | This function return DBHelper 72 | :return: DBHelper 73 | """ 74 | return self 75 | 76 | def __exit__(self, *args, **kwargs): 77 | """ This function will close connection and cursor if connection is open """ 78 | if self._connection: 79 | self._cursor.close() 80 | self._connection.close() 81 | 82 | def execute(self, sql: str, params: tuple = ()) -> bool: 83 | """ This function execute sql query with params """ 84 | try: 85 | self._cursor.execute(sql, params) 86 | self.last_execute = True 87 | 88 | return True 89 | except Exception as e: 90 | logger.error(e) 91 | self.last_execute = False 92 | 93 | return False 94 | 95 | def fetchall(self) -> list: 96 | """ This function return sql queries result """ 97 | try: 98 | if self.last_execute: 99 | return self._cursor.fetchall() 100 | 101 | logger.info("Before calling the function, a successful SQL execution is required.") 102 | except Exception as e: 103 | logger.error("Error: {}".format(e)) 104 | 105 | return [] 106 | 107 | def fetchone(self) -> dict: 108 | """ This function return sql queries result """ 109 | try: 110 | if self.last_execute: 111 | return self._cursor.fetchone() 112 | 113 | logger.info("Before calling the function, a successful SQL execution is required.") 114 | except Exception as e: 115 | logger.error("Error: {}".format(e)) 116 | 117 | return {} 118 | -------------------------------------------------------------------------------- /heybooster/helpers/database/opensearch.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests.auth import HTTPBasicAuth 3 | 4 | 5 | class OpenSearchHelper: 6 | """ OpenSearch (ElasticSearch) Helper """ 7 | GET = "get" 8 | PUT = "put" 9 | POST = "post" 10 | 11 | def __init__(self, url: str, index: str, username: str = None, password: str = None): 12 | self.url = url 13 | self.index = index 14 | self.auth = None 15 | self.username = username 16 | self.password = password 17 | 18 | if all([username, password]): 19 | self.__set_auth() 20 | 21 | self.__check_url() 22 | 23 | def get_url(self, path: str) -> str: 24 | """ This function return url with given path """ 25 | return f"{self.url}/{self.index}/{path}" 26 | 27 | def __perform_request(self, method: str, url: str, payload: dict = {}, expected_status: int = 200) -> dict: 28 | """ 29 | This function send request and check expected status after return respose 30 | """ 31 | response = requests.request( 32 | method=method, 33 | url=url, 34 | auth=self.auth, 35 | headers={ 36 | "Content-Type": "application/json" 37 | }, 38 | json=payload 39 | ) 40 | 41 | if response.status_code != expected_status: 42 | raise Exception(f"Reponse Status Code -> {response.status_code} \n Message -> {response.text}") 43 | 44 | return response.json() 45 | 46 | def __set_auth(self): 47 | """ 48 | This function set request authentication 49 | """ 50 | self.auth = HTTPBasicAuth(username=self.username, password=self.password) 51 | 52 | def __check_url(self): 53 | """ 54 | This function check url is working 55 | """ 56 | try: 57 | response = self.__perform_request(method=OpenSearchHelper.GET, url=self.get_url(path="_search")) 58 | 59 | if not response: 60 | raise BaseException("URL Not Working") 61 | 62 | except Exception as exception: 63 | raise Exception(exception) 64 | 65 | def update_or_insert(self, data: dict, _id: str = None) -> dict: 66 | """ 67 | This function insert data or update date (if has _id) 68 | """ 69 | try: 70 | response = self.__perform_request( 71 | method=OpenSearchHelper.POST, 72 | url=self.get_url(path="_doc" if not _id else f"_doc/{_id}"), 73 | payload=data, 74 | expected_status=200 if _id else 201 75 | ) 76 | 77 | return response 78 | except Exception as exception: 79 | raise Exception(exception) 80 | 81 | def search(self, size: int = 10, sort: str = "_id:desc", **kwargs): 82 | """ 83 | This function returns search response 84 | """ 85 | try: 86 | path = f"_search" 87 | url = self.get_url(path=path) 88 | sort_key = sort.split(":")[0] 89 | sort_order = sort.split(":")[1] 90 | 91 | payload = { 92 | "sort": [ 93 | { 94 | sort_key: { 95 | "order": sort_order 96 | } 97 | } 98 | ], 99 | "size": size, 100 | "query": { 101 | "match": { 102 | **kwargs 103 | } 104 | } 105 | } 106 | 107 | response = self.__perform_request( 108 | method=OpenSearchHelper.GET, 109 | url=url, 110 | payload=payload, 111 | ) 112 | 113 | return response 114 | except Exception as exception: 115 | raise Exception(exception) 116 | 117 | def get(self, payload: dict = {}): 118 | """ 119 | This function is return data for payload 120 | """ 121 | try: 122 | return requests.get(url=self.get_url(path="_search"), json=payload, auth=self.auth) 123 | except Exception as e: 124 | raise Exception(e) 125 | -------------------------------------------------------------------------------- /heybooster/helpers/hubspot/contacts.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | from urllib.parse import quote_plus 4 | 5 | class HubSpotContactsAPI(): 6 | 7 | def __init__(self, api_key): 8 | self.api_key = api_key 9 | self.base_url = 'https://api.hubapi.com/contacts/v1/' 10 | 11 | def get_contact_by_email(self, email): 12 | """ 13 | Returns contact data for given email 14 | :email: str, contact email 15 | """ 16 | encoded_email = quote_plus(email) 17 | url = f'{self.base_url}/contact/email/{encoded_email}/profile' 18 | params = { 19 | 'hapikey': self.api_key 20 | } 21 | resp = requests.get(url, params=params) 22 | 23 | if resp.status_code == 200: 24 | return resp.json() 25 | elif resp.status_code == 404: 26 | return None 27 | else: 28 | raise Exception(f'this response status code is not considered before - status code: {resp.status_code}') 29 | 30 | def update_contact_by_email(self, email, properties): 31 | """ 32 | Updates contact for given email properties with given properties 33 | :email: contact email 34 | :properties: list, 35 | return True if updated successfully 36 | """ 37 | encoded_email = quote_plus(email) 38 | data = json.dumps({ 39 | 'properties': properties 40 | }) 41 | params = { 42 | 'hapikey': self.api_key 43 | } 44 | url = f'{self.base_url}/contact/email/{encoded_email}/profile' 45 | headers = { 46 | 'Content-Type': 'application/json' 47 | } 48 | resp = requests.post(data=data, url=url, params=params, headers=headers) 49 | 50 | if resp.status_code == 204: 51 | return True 52 | elif resp.status_code == 400: 53 | raise Exception(f'{resp.text}') 54 | elif resp.status_code == 401: 55 | raise Exception(f'{resp.text}') 56 | elif resp.status_code == 404: 57 | raise Exception(f'{resp.text}') 58 | elif resp.status_code == 500: 59 | raise Exception(f'{resp.text}') 60 | else: 61 | raise Exception(f'this response status code is not considered before - status code: {resp.status_code}') 62 | 63 | def create_contact(self, properties=[]): 64 | """ 65 | Creates contact with given properties (email must be included) 66 | :properties: list 67 | return Response.json() 68 | """ 69 | email_prop_exists = False 70 | for prop in properties: 71 | if prop['property'] == 'email' and prop['value']: 72 | email_prop_exists = True 73 | if not email_prop_exists: 74 | raise Exception('Email must be included') 75 | url = f'{self.base_url}/contact' 76 | params = { 77 | 'hapikey': self.api_key 78 | } 79 | data = json.dumps({ 80 | 'properties': properties 81 | }) 82 | headers = { 83 | 'Content-Type': 'application/json' 84 | } 85 | resp = requests.post(data=data, url=url, params=params, headers=headers) 86 | 87 | if resp.status_code == 200: 88 | return resp.json() 89 | elif resp.status_code == 400: 90 | raise Exception(f'{resp.text}') 91 | elif resp.status_code == 409: 92 | raise Exception(f'{resp.text}') 93 | else: 94 | raise Exception(f'this response status code is not considered before - status code: {resp.status_code}') 95 | 96 | def delete_contact_by_email(self, email: str): 97 | """ 98 | Deletes contact for given email 99 | :email: contact email 100 | return response.json() 101 | """ 102 | contact = self.get_contact_by_email(email) 103 | 104 | if contact: 105 | contact_id = contact['vid'] 106 | url = f'{self.base_url}/contact/vid/{contact_id}' 107 | params = { 108 | 'hapikey': self.api_key 109 | } 110 | resp = requests.delete(url, params=params) 111 | 112 | if resp.status_code == 200: 113 | return resp.json() 114 | elif resp.status_code == 401: 115 | raise Exception(f'{resp.text}') 116 | elif resp.status_code == 404: 117 | raise Exception(f'{resp.text}') 118 | elif resp.status_code == 500: 119 | raise Exception(f'{resp.text}') 120 | else: 121 | raise Exception(f'this response status code is not considered before - status code: {resp.status_code}') 122 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | heybooster. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /heybooster/helpers/email/sendpulse.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from .utils import HTTPMethods, AuthenticationError, SendProcessFailureError 3 | from .statics import BASE_URL, ACCESS_URL_PATH, SMTP_URL_PATH 4 | 5 | 6 | class SendPulse: 7 | """ SendPulse SMTP API """ 8 | 9 | def __init__(self, client_id: str, client_secret: str): 10 | """ 11 | :client_id: string, SendPulse client id 12 | :client_secret: string, SendPulse client secret key 13 | """ 14 | self._client_id = client_id 15 | self._client_secret = client_secret 16 | self._access_token = self.__get_access_token() 17 | 18 | def get_url(self, path: str = ''): 19 | """ 20 | Concanates path and url 21 | 22 | :path: string 23 | 24 | return concanated url 25 | """ 26 | return BASE_URL + path 27 | 28 | def __get_header(self, use_access_token: bool = True): 29 | """ 30 | Returns needed header 31 | 32 | return dict 33 | """ 34 | return {'Authorization': 'Bearer ' + self._access_token} if use_access_token else {} 35 | 36 | def __perform_request(self, path: str, http_method: HTTPMethods, params: dict = {}, body: dict = {}, use_access_token: bool = True) -> dict: 37 | """ 38 | Sends request and refreshes access_token if necessary 39 | 40 | :path: string, added to end of the base url 41 | :http_method: int, enum from HTTPMethods 42 | :params: dict, request parameters 43 | :json: dict, request body 44 | :use_access_token: bool, whether header must include access_token 45 | :retry: bool, if true retry on authentication failure 46 | 47 | return response body from Sendpulse 48 | """ 49 | request = getattr(requests, http_method.name) 50 | 51 | return request( 52 | url=self.get_url(path), 53 | headers=self.__get_header(use_access_token=use_access_token), 54 | params=params, 55 | json=body 56 | ) 57 | 58 | def refresh_token(self): 59 | """ 60 | Gets new access token 61 | """ 62 | self._access_token = self.__get_access_token() 63 | 64 | def __get_access_token(self) -> str: 65 | """ 66 | Gets access token 67 | 68 | :path: string, added to end of the base url 69 | 70 | return str, access token 71 | """ 72 | response = self.__perform_request(path=ACCESS_URL_PATH, http_method=HTTPMethods.post, use_access_token=False, body={ 73 | 'grant_type': 'client_credentials', 74 | 'client_id': self._client_id, 75 | 'client_secret': self._client_secret, 76 | }) 77 | 78 | if response.status_code == 200: 79 | return response.json()['access_token'] 80 | else: 81 | raise AuthenticationError 82 | 83 | def send_email_with_template(self, subject: str, template_id: str, variables: dict, from_name: str, from_email: str, to_data: list): 84 | """ 85 | Sends email by using templates on Sendpulse 86 | 87 | :subject: string, email subject 88 | :template_id: string, template_id on SenpPulse 89 | :variables: dict, {: } 90 | :from_name: string, email sender's name 91 | :from_email: string, email sender's adress 92 | :to_data: list, [{'name': , 'email': }] 93 | 94 | return response body from Sendpulse 95 | """ 96 | try: 97 | return self.__perform_request(path=SMTP_URL_PATH, http_method=HTTPMethods.post, body={'email': { 98 | 'subject': subject, 99 | 'template': { 100 | 'id': template_id, 101 | 'variables': variables 102 | }, 103 | 'from': { 104 | 'name': from_name, 105 | 'email': from_email 106 | }, 107 | 'to': to_data 108 | }}).json() 109 | except: 110 | raise SendProcessFailureError 111 | 112 | def get_information_for_a_list_of_email(self, emails: list): 113 | """ 114 | Gets information for a list of email 115 | 116 | :emails: list, list of emails 117 | 118 | return response body from Sendpulse 119 | """ 120 | if len(emails) > 500: 121 | raise ValueError('Maximum number of emails is 500') 122 | 123 | path = SMTP_URL_PATH + "/info" 124 | 125 | return self.__perform_request(path=path, http_method=HTTPMethods.post, body={'emails': emails}).json() 126 | 127 | def send_email(self, subject: str, html: str, text: str, from_name: str, from_email: str, to_data: list): 128 | """ 129 | Sends email by using templates on Sendpulse 130 | 131 | :subject: string, email subject 132 | :html: string, html of email encoded in Base64 133 | :text: string, text of email 134 | :from_name: string, email sender's name 135 | :from_email: string, email sender's adress 136 | :to_data: list, [{'name': , 'email': }] 137 | 138 | return response body from Sendpulse 139 | """ 140 | try: 141 | return self.__perform_request(path=SMTP_URL_PATH, http_method=HTTPMethods.post, body={'email': { 142 | 'subject': subject, 143 | 'html': html, 144 | 'text': text, 145 | 'from': { 146 | 'name': from_name, 147 | 'email': from_email 148 | }, 149 | 'to': to_data 150 | }}).json() 151 | except: 152 | raise SendProcessFailureError 153 | -------------------------------------------------------------------------------- /heybooster/helpers/database/mongodb.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | from pymongo.command_cursor import CommandCursor 3 | 4 | 5 | class MongoDBHelper: 6 | """ MongoDB Helper""" 7 | 8 | def __init__(self, **kwargs): 9 | """ 10 | Init Function 11 | :param kwargs: 12 | :return: 13 | """ 14 | self.client = pymongo.MongoClient(kwargs['uri']) 15 | self._database = getattr(self.client, kwargs['database']) 16 | 17 | def __enter__(self): 18 | """ 19 | This function return self 20 | :return: 21 | """ 22 | return self 23 | 24 | def __exit__(self, *args, **kwargs): 25 | """ 26 | This function connection close when context manager end 27 | :param kwargs: 28 | :return: 29 | """ 30 | try: 31 | if self.client: 32 | self.client.close() 33 | except: 34 | pass 35 | 36 | @DeprecationWarning 37 | def insert(self, collection: str, data: dict) -> object: 38 | """ 39 | This function insert data in collection 40 | :param collection: 41 | :param data: 42 | :return:object 43 | """ 44 | return self._database[collection].insert(data) 45 | 46 | def insert_one(self, collection: str, data: dict) -> pymongo.InsertOne: 47 | """ 48 | This function insert data in collection 49 | :param collection: str 50 | :param data: dict 51 | :return: pymongo.InsertOne 52 | """ 53 | return self._database[collection].insert_one(data) 54 | 55 | def find_one(self, collection: str, query: dict, projection: dict = {}, default: object = None, **kwargs) -> dict: 56 | """ 57 | This function get query result in collection 58 | :param collection: str 59 | :param query: dict 60 | :param projection: dict 61 | :return: dict 62 | """ 63 | try: 64 | if bool(projection): 65 | return self._database[collection].find_one(query, projection, **kwargs) 66 | else: 67 | return self._database[collection].find_one(query, **kwargs) 68 | except Exception as e: 69 | if default: 70 | return default 71 | 72 | raise Exception(e) 73 | 74 | def find(self, collection: str, query: dict, projection: dict = {}, default: object = None, **kwargs) -> list: 75 | """ 76 | This function list query results in collection 77 | :param collection: str 78 | :param query: dict 79 | :param projection: dict 80 | :param: default: Object 81 | :return: list 82 | """ 83 | try: 84 | if bool(projection): 85 | return self._database[collection].find(query, projection, **kwargs) 86 | else: 87 | return self._database[collection].find(query, **kwargs) 88 | except Exception as e: 89 | if default: 90 | return default 91 | 92 | raise Exception(e) 93 | 94 | def find_and_modify(self, collection: str, query: dict, **kwargs) -> list: 95 | """ 96 | This function find and modify data in collection 97 | :param collection: 98 | :param query: 99 | :param kwargs: 100 | :return: list 101 | """ 102 | try: 103 | return self._database[collection].find_and_modify( 104 | query=query, 105 | update={"$set": kwargs}, 106 | upsert=False, 107 | full_response=True 108 | ) 109 | except Exception as e: 110 | raise Exception(e) 111 | 112 | def aggregate(self, collection: str, pipeline: list, options: dict = {}, default: object = None) -> CommandCursor: 113 | """ 114 | This function aggregations in mongodb 115 | :param collection: 116 | :param pipeline: 117 | :param options: 118 | :param default: 119 | :return object: 120 | """ 121 | try: 122 | if bool(options): 123 | return self._database[collection].aggregate(pipeline, options) 124 | else: 125 | return self._database[collection].aggregate(pipeline) 126 | except Exception as e: 127 | if default: 128 | return default 129 | raise Exception(e) 130 | 131 | def remove(self, collection: str, query: dict): 132 | """ 133 | This function delete data in collection 134 | :param collection: 135 | :param query: 136 | :return:object 137 | """ 138 | try: 139 | self._database[collection].remove(query) 140 | except Exception as e: 141 | raise Exception(e) 142 | 143 | def delete_many(self, collection: str, query: dict): 144 | """ 145 | This function delete many data in collection 146 | :param collection: 147 | :param query: 148 | :return:object 149 | """ 150 | try: 151 | self._database[collection].delete_many(query) 152 | except Exception as e: 153 | raise Exception(e) 154 | 155 | def delete_one(self, collection: str, query: dict): 156 | """ 157 | This function delete data in collection 158 | :param collection: 159 | :param query: 160 | :return:object 161 | """ 162 | try: 163 | self._database[collection].delete_one(query) 164 | except Exception as e: 165 | raise Exception(e) 166 | 167 | def update(self, collection: str, query: dict, update: dict) -> object: 168 | """ 169 | This function update data in collection 170 | :param collection: 171 | :param query: 172 | :param update: 173 | :return:object 174 | """ 175 | try: 176 | return self._database[collection].update(query, update) 177 | except Exception as e: 178 | raise Exception(e) 179 | 180 | def update_many(self, collection: str, query: dict, update: dict) -> object: 181 | """ 182 | This function update many data in collection 183 | :param collection: 184 | :param query: 185 | :param update: 186 | :return:object 187 | """ 188 | try: 189 | return self._database[collection].update_many(query, update) 190 | except Exception as e: 191 | raise Exception(e) 192 | 193 | def close(self): 194 | """ 195 | This function call __exit__ function for close mongo connection 196 | """ 197 | return self.__exit__ 198 | --------------------------------------------------------------------------------