├── MANIFEST.in ├── .others ├── bot.png └── telegram.py.bak ├── .gitignore ├── .env ├── requirements.txt ├── notifly ├── __init__.py ├── gpu_stats.py ├── slack.py ├── discord.py ├── telegram.py └── tf_notifier.py ├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── proposal.md │ ├── documentation.md │ └── feature.md ├── workflows │ ├── python-publish.yml │ ├── python-test.yml │ └── python-app.yml ├── pull_request_template.md └── config.yml ├── req.txt ├── CHANGELOG.txt ├── setup.py ├── LICENSE ├── test.py ├── temp.py ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── docs ├── index.html ├── slack.html ├── discord.html ├── telegram.html └── tf_notifier.html └── README.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-include *.txt *.py -------------------------------------------------------------------------------- /.others/bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rexdivakar/Notifly/HEAD/.others/bot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by .ignore support plugin (hsz.mobi) 3 | .idea 4 | !/temp.py 5 | .vscode 6 | test.py 7 | __pycache__/ 8 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | TOKEN="https://discord.com/api/webhooks/794553880427888641/rUegx2re6-flz7ESZyvIRaBLUMY6e1BhctMXpv2hWA1Zi4I2ulNmuqRSTv9KTI9cPq2Y" 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.24.0 2 | numpy==1.22.2 3 | matplotlib==3.2.2 4 | slackclient==2.9.3 5 | psutil==5.7.3 6 | python-dotenv==0.15.0 -------------------------------------------------------------------------------- /notifly/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Notifly Documentation 3 | 4 | Discord: (object): Discord wrapper to send message, images to the channel using Webhooks 5 | Slack (object): Slack wrapper to send message, images to the channel using API 6 | Telegram (object): Telegram Web Client wrapper to send images, files over the bot using API 7 | """ 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Submit a bug report to help us improve 4 | labels: "bug" 5 | --- 6 | 7 | ## 🐛 Bug Report 8 | 9 | (A clear and concise description of what the bug is.) 10 | 11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)? 12 | 13 | (Write your answer here.) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💥 Proposal 3 | about: Propose a non-trivial change to Notifly 4 | labels: "proposal" 5 | --- 6 | 7 | ## 💥 Proposal 8 | 9 | (A clear and concise description of what the proposal is.) 10 | 11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)? 12 | 13 | (Write your answer here.) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📚 Documentation 3 | about: Report an issue related to documentation 4 | labels: "documentation" 5 | --- 6 | 7 | ## 📚 Documentation 8 | 9 | (A clear and concise description of what the issue is.) 10 | 11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)? 12 | 13 | (Write your answer here.) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature 3 | about: Submit a proposal for a new feature 4 | labels: "feature" 5 | --- 6 | 7 | ## 🚀 Feature 8 | 9 | (A clear and concise description of what the feature is.) 10 | 11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)? 12 | 13 | (Write your answer here.) 14 | 15 | ## Motivation 16 | 17 | (Please outline the motivation for the proposal.) 18 | 19 | ## Pitch 20 | 21 | (Please explain why this feature should be implemented and how it would be used.) 22 | -------------------------------------------------------------------------------- /req.txt: -------------------------------------------------------------------------------- 1 | tensorflow==2.15.0 2 | matplotlib 3 | slackclient 4 | python-dotenv 5 | psutil 6 | aiohttp>=3.9.2 # not directly required, pinned by Snyk to avoid a vulnerability 7 | fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability 8 | numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability 9 | pillow>=10.2.0 # not directly required, pinned by Snyk to avoid a vulnerability 10 | setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability 11 | werkzeug>=2.3.8 # not directly required, pinned by Snyk to avoid a vulnerability 12 | wheel>=0.38.0 # not directly required, pinned by Snyk to avoid a vulnerability -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | 1.0 (17/10/2020) 5 | ------------------ 6 | - First Release 7 | 8 | 1.1.5 (26/10/2020) 9 | ------------------ 10 | - Basic Fix and Major changes for Telegram module 11 | - Added Discord Bot feature 12 | 13 | 1.2.0 (19/11/2020) 14 | ------------------ 15 | - Added Notification Handler for discord, slack 16 | - Added Wrapper for Tensorflow Callback 17 | - Minor Fixes 18 | 19 | 1.3.0 (17/1/2021) 20 | ------------------ 21 | - Added functionalities to monitor and track hardware info during model training 22 | - Minor Fixes 23 | - Updated Documentation 24 | 25 | 1.3.1 (21/2/2021) 26 | ------------------ 27 | - Bug fix -- CPU instance error on runtime 28 | - Added Wiki pages 29 | 30 | 1.3.2 (23/5/2022) 31 | ------------------ 32 | - Resolved Buffer Overflow in NumPy Package (CVE-2021-33430) 33 | - Updated Wiki pages 34 | 35 | 1.3.3 (17/02/2024) 36 | ------------------ 37 | - Upgraded the package version to support latest python 3.11 and above -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: __token__ 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload --verbose dist/* 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from pathlib import Path 3 | 4 | classifiers = [ 5 | 'Development Status :: 5 - Production/Stable', 6 | 'Intended Audience :: Education', 7 | 'Operating System :: Microsoft :: Windows :: Windows 10', 8 | 'License :: OSI Approved :: MIT License', 9 | 'Programming Language :: Python :: 3' 10 | ] 11 | 12 | # Get the long description from the README file 13 | long_description = Path("README.md").read_text(encoding="utf-8") 14 | 15 | setup( 16 | name='notifly', 17 | version='1.3.3', 18 | description='Notification on the fly !', 19 | long_description=long_description, 20 | long_description_content_type='text/markdown', 21 | url='https://github.com/rexdivakar/Telegram-Notifly', 22 | author='Divakar R, Sanchit Jain', 23 | author_email='rexdivakar@hotmail.com , sanchitjain1996@gmail.com', 24 | license='MIT', 25 | classifiers=classifiers, 26 | keywords='Bot Notification', 27 | packages=['notifly'], 28 | install_requires=['requests', 'numpy==1.19.3', 'matplotlib', 'slackclient', 'python-dotenv', 'psutil'] 29 | ) 30 | -------------------------------------------------------------------------------- /.github/workflows/python-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Pytest 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - '*' 11 | pull_request: 12 | branches: 13 | - main 14 | - 'releases/**' 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | python-version: [3.11] 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install flake8 pytest 34 | if [ -f req.txt ]; then pip install -r req.txt; fi 35 | 36 | - name: Test with pytest 37 | run: | 38 | pytest test.py -v --disable-warnings 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 rex_divakar 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 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Lint Check 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - '*' 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python 3.11 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: 3.11 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install flake8 pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | #### Issue Number 2 | ISSUE # 3 | 7 | 8 | #### Describe the changes you've made 9 | 12 | 13 | #### Describe if there is any unusual behaviour of your code(Write `NA` if there isn't) 14 | 17 | 18 | #### Additional context (OPTIONAL) 19 | 22 | 23 | #### Test plan (OPTIONAL) 24 | 28 | 29 | #### Checklist 30 | 34 | - [ ] My code follows the code style of this project. 35 | - [ ] I have performed a self-review of my own code. 36 | - [ ] My change requires a change to the documentation. 37 | - [ ] I have read the **[CONTRIBUTING](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)** document. 38 | - [ ] I have updated the documentation accordingly. 39 | - [ ] I have commented my code, particularly in hard-to-understand areas. 40 | - [ ] My changes generate no new warnings. 41 | - [ ] I have added tests that prove my fix is effective or that my feature works. 42 | - [ ] The title of my pull request is a short description of the requested changes. 43 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | from notifly import discord 3 | import tensorflow as tf 4 | import os 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' 10 | 11 | 12 | def test(): 13 | ssl._create_default_https_context = ssl._create_unverified_context 14 | token = os.getenv('TOKEN') 15 | notifier = discord.Notifier(token) 16 | 17 | class TestCallback(tf.keras.callbacks.Callback): 18 | 19 | @notifier.notify_on_epoch_begin(epoch_interval=1, graph_interval=1, hardware_stats_interval=1) 20 | def on_epoch_begin(self, epoch, logs=None): 21 | pass 22 | 23 | @notifier.notify_on_epoch_end(epoch_interval=1, graph_interval=1, hardware_stats_interval=1) 24 | def on_epoch_end(self, epoch, logs=None): 25 | pass 26 | 27 | @notifier.notify_on_train_begin() 28 | def on_train_begin(self, logs=None): 29 | pass 30 | 31 | @notifier.notify_on_train_end() 32 | def on_train_end(self, logs=None): 33 | pass 34 | 35 | fashion_mnist = tf.keras.datasets.fashion_mnist 36 | (train_images, train_labels), (_, _) = fashion_mnist.load_data() 37 | 38 | model = tf.keras.Sequential([ 39 | tf.keras.layers.Flatten(input_shape=(28, 28)), 40 | tf.keras.layers.Dense(2, activation='relu'), 41 | tf.keras.layers.Dense(10) 42 | ]) 43 | 44 | model.compile(optimizer='adam', 45 | loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 46 | metrics=['accuracy']) 47 | 48 | model.fit(train_images, train_labels, epochs=1, callbacks=[TestCallback()]) 49 | 50 | 51 | test() 52 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for welcome - https://github.com/behaviorbot/welcome 2 | 3 | # Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome 4 | # Comment to be posted to on first time issues 5 | 6 | newIssueWelcomeComment: > 7 | Hello there!👋 Welcome to the project!🚀⚡ 8 | 9 | 10 | Thank you and congrats🎉 for opening your very first issue in this project. 11 | Please adhere to our [Code of Conduct](https://github.com/rexdivakar/Notifly/blob/main/CODE_OF_CONDUCT.md). 12 | Please make sure not to start working on the issue, unless you get assigned to it.😄 13 | Feel free to join us on [OpenSource-Dev-Community](https://discord.gg/tTJQxvSaDP)✨ Hope you have a great time there!😄 14 | 15 | 16 | # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome 17 | # Comment to be posted to on PRs from first time contributors in your repository 18 | 19 | newPRWelcomeComment: > 20 | Hello there!👋 Welcome to the project!💖 21 | 22 | 23 | Thank you and congrats🎉 for opening your first pull request in this project. 24 | Please make sure you have followed our [Contributing Guidelines](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md).🙌🙌 We will get back to you as soon as we can 😄. 25 | Feel free to join us on [OpenSource-Dev-Community](https://discord.gg/tTJQxvSaDP)✨ Hope you have a great time there!😄 26 | 27 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge 28 | # Comment to be posted to on pull requests merged by a first time user 29 | 30 | firstPRMergeComment: > 31 | Congrats on merging your first pull request! 🎉 All the best for your amazing open source journey ahead 🚀. 32 | -------------------------------------------------------------------------------- /notifly/gpu_stats.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE 2 | from distutils import spawn 3 | import os 4 | import sys 5 | import platform 6 | 7 | 8 | def gpu(): 9 | """ 10 | Monitors the hardware info on the runtime. 11 | 12 | Returns: 13 | The list of parameters containing hardware info 14 | """ 15 | if platform.system() == "Windows": 16 | try: 17 | nvidia_smi = spawn.find_executable('nvidia-smi') 18 | except FileNotFoundError as fn: 19 | print(fn) 20 | 21 | if nvidia_smi is None: 22 | nvidia_smi = "%s\\Program Files\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe" % os.environ['systemdrive'] 23 | else: 24 | nvidia_smi = "nvidia-smi" 25 | 26 | # Get ID, processing and memory utilization for all GPUs (deviceIds, uuid, gpuUtil, memTotal, memUsed, memFree, 27 | # driver,gpu_name,serial,display_mode,display_active, temp_gpu) 28 | try: 29 | p = Popen([nvidia_smi, 30 | "--query-gpu=index,uuid,utilization.gpu,memory.total,memory.used,memory.free,driver_version,name," 31 | "gpu_serial,display_active,display_mode,temperature.gpu", 32 | "--format=csv,noheader,nounits"], stdout=PIPE) 33 | stdout, stderr = p.communicate() 34 | if p is not None: 35 | x = stdout.decode('UTF-8').split(',') 36 | if len(x) > 1: 37 | return x 38 | 39 | print('No Gpu Found, continuing with CPU instance') 40 | 41 | except SystemError as stderr: 42 | print('Unable to establish a communication with GPU', stderr) 43 | sys.exit(1) 44 | except FileNotFoundError as fn: 45 | print('Unable to find GPU connection in your device, Proceeding using CPU instance') 46 | -------------------------------------------------------------------------------- /temp.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | from notifly import tf_notifier 3 | import tensorflow as tf 4 | from dotenv import load_dotenv 5 | import os 6 | 7 | 8 | load_dotenv() 9 | 10 | ssl._create_default_https_context = ssl._create_unverified_context 11 | token = os.getenv('TOKEN') 12 | notifier = tf_notifier.TfNotifier(token=token, platform='discord') 13 | 14 | 15 | class TestCallback(tf.keras.callbacks.Callback): 16 | 17 | @notifier.notify_on_epoch_begin(epoch_interval=1, graph_interval=10) 18 | def on_epoch_begin(self, epoch, logs=None): 19 | pass 20 | 21 | @notifier.notify_on_epoch_end(epoch_interval=1, graph_interval=10) 22 | def on_epoch_end(self, epoch, logs=None): 23 | pass 24 | 25 | @notifier.notify_on_train_begin() 26 | def on_train_begin(self, logs=None): 27 | pass 28 | 29 | @notifier.notify_on_train_end() 30 | def on_train_end(self, logs=None): 31 | pass 32 | 33 | 34 | fashion_mnist = tf.keras.datasets.fashion_mnist 35 | 36 | (train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data() 37 | 38 | class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 39 | 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'] 40 | 41 | model = tf.keras.Sequential([ 42 | tf.keras.layers.Flatten(input_shape=(28, 28)), 43 | tf.keras.layers.Dense(5, activation='relu'), 44 | tf.keras.layers.Dense(10) 45 | ]) 46 | 47 | model.compile(optimizer='adam', 48 | loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 49 | metrics=['accuracy']) 50 | 51 | model.fit(train_images, train_labels, epochs=5, callbacks=[TestCallback()]) 52 | 53 | test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2) 54 | 55 | print('\nTest accuracy:', test_acc) 56 | -------------------------------------------------------------------------------- /notifly/slack.py: -------------------------------------------------------------------------------- 1 | """Slack-API call wrapper""" 2 | 3 | import slack 4 | from slack.errors import SlackApiError 5 | from notifly.tf_notifier import TfNotifier 6 | 7 | 8 | class Notifier(TfNotifier): 9 | def __init__(self, token, channel='general'): 10 | """ 11 | Initialize the slack webclient instance using API. 12 | 13 | Args: 14 | token (string): API token [Mandatory]. 15 | channel (string): Post's the notification to it's corresponding channel [default = #general]. 16 | Returns: 17 | The output of slack API_test which verify the Authentication protocol. 18 | Raises: 19 | SlackApiError. 20 | """ 21 | self.__client = slack.WebClient(token=token) 22 | self.__channel = channel 23 | 24 | try: 25 | self.__client.api_test()['ok'] 26 | except SlackApiError as api_err: 27 | print(f"Got an error: {api_err.response['error']}") 28 | exit(1) 29 | 30 | def send_message(self, msg) -> object: 31 | """ 32 | Function to post message to the slack channel. 33 | 34 | Args: 35 | msg (string): Enter your message to post. 36 | Returns: 37 | Outputs the response of message on post operation. 38 | Raises: 39 | SlackApiError 40 | """ 41 | try: 42 | return self.__client.chat_postMessage(channel=self.__channel, text=msg) 43 | except SlackApiError as api_err: 44 | print(f"Got an error: {api_err.response['error']}") 45 | exit(1) 46 | 47 | def send_file(self, file_path) -> object: 48 | """ 49 | Function to post an file to the slack channel. 50 | 51 | Args: 52 | file_path (string): Enter the path of the file to be sent. 53 | Returns: 54 | Outputs the response of message on post operation. 55 | Raises: 56 | FileNotFoundError 57 | """ 58 | try: 59 | return self.__client.files_upload(file=file_path, channels=self.__channel) 60 | except FileNotFoundError as fl_err: 61 | print(fl_err) 62 | -------------------------------------------------------------------------------- /notifly/discord.py: -------------------------------------------------------------------------------- 1 | """Discord API webhooks wrapper""" 2 | 3 | import requests 4 | from notifly.tf_notifier import TfNotifier 5 | from requests import exceptions 6 | 7 | 8 | class Notifier(TfNotifier): 9 | 10 | class __AuthError(Exception): 11 | """ 12 | Authentication Exception 13 | """ 14 | pass 15 | 16 | def __init__(self, webhooks): 17 | """ 18 | Initialize the Discord Webhook instance 19 | 20 | Args: 21 | webhooks (basestring): Discord webhook token [Mandatory] 22 | Returns: 23 | The response of webhook status 24 | Raises: 25 | Exception, AuthError, MissingSchema 26 | """ 27 | self.__webhooks = webhooks 28 | self.payload = None 29 | 30 | try: 31 | if requests.get(self.__webhooks).status_code == 401: 32 | raise Notifier.__AuthError('Invalid Webhook') 33 | except exceptions.ConnectionError as err: 34 | print('HTTPS Connection Error - Unable to reach the destination') 35 | exit(1) 36 | except Notifier.__AuthError as ty_err: 37 | print(ty_err) 38 | exit(1) 39 | except requests.models.MissingSchema as ms_err: 40 | print(ms_err) 41 | exit(1) 42 | 43 | def send_message(self, msg) -> object: 44 | """ 45 | Function to post message to the discord channel 46 | 47 | Args: 48 | msg (string): Posts message to the discord channel [String] [UTF-8] 49 | Returns: 50 | The response to the message on post operation. 51 | Raises: 52 | ConnectionError 53 | """ 54 | payload = {'content': str(msg)} 55 | try: 56 | return requests.post(url = self.__webhooks, data = payload) 57 | except exceptions.ConnectionError as cer: 58 | print(cer) 59 | exit(1) 60 | 61 | def send_file(self, file_path) -> object: 62 | """ 63 | Function to post an image to the discord channel 64 | 65 | Args: 66 | file_path (string): Enter the path of the file to be sent 67 | Returns: 68 | The response to the message on post operation. 69 | Raises: 70 | FileNotFoundError, OverflowError 71 | """ 72 | try: 73 | self.payload = {'file': open(file_path, 'rb')} 74 | except FileNotFoundError as fl_er: 75 | print(fl_er) 76 | exit(1) 77 | try: 78 | return requests.post(url = self.__webhooks, files = self.payload) 79 | except OverflowError as err: 80 | print('Size Overflow Error', err) 81 | exit(1) 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at tawishisharma1@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue with the owners of this repository before making a change. 4 | 5 | ## Maintainers 6 | 7 | #### Divakar -- Discord id (root#5998) 8 | #### Sanchit -- Discord id (sanchit#5988) 9 | 10 | Discord server - https://discord.gg/e47wmTW 11 | 12 | Please note we have a code of conduct, please follow it in all your interactions with the project. 13 | 14 | ## Issues 15 | 16 | ### Issues are very valuable to this project. 17 | * Ideas are a valuable source of contributions others can make 18 | * Problems show where this project is lacking 19 | * With a question you show where contributors can improve the user experience

20 | _Thank you_ for creating them. 21 | 22 | ## Pull Requests 23 | ### Pull requests are, a great way to get your ideas into this repository. 24 | When deciding if I merge in a pull request I look at the following things: 25 | #### Does it state intent 26 | You should be clear which problem you're trying to solve with your contribution. 27 | For example: 28 | > Add link to code of conduct in README.md 29 | 30 | Doesn't tell me anything about why you're doing that 31 | 32 | > Add link to code of conduct in README.md because users don't always look in the CONTRIBUTING.md 33 | Tells me the problem that you have found, and the pull request shows me the action you have taken to solve it. 34 | #### Is it of good quality 35 | * There are no spelling mistakes 36 | * It reads well 37 | * For english language contributions: Has a good score on Grammarly or Hemingway App 38 | #### Does it move this repository closer to my vision for the repository 39 | Objecive for our repository is to build a package that acts as a notification medium which sends us about the model training parameters over telegram/discord/slack.We can also use this package as a notification medium for sending texts, picture and more. 40 | #### Does it follow the contributor covenant 41 | This repository has a [code of conduct][1], remove things that do not respect it. 42 | 43 | ## Our Pledge 44 | 45 | In the interest of fostering an open and welcoming environment, we as 46 | contributors and maintainers pledge to making participation in our project and 47 | our community a harassment-free experience for everyone, regardless of age, body 48 | size, disability, ethnicity, gender identity and expression, level of experience, 49 | nationality, personal appearance, race, religion, or sexual identity and 50 | orientation. 51 | 52 | ### Our Standards 53 | 54 | Examples of behavior that contributes to creating a positive environment 55 | include: 56 | 57 | * Using welcoming and inclusive language 58 | * Being respectful of differing viewpoints and experiences 59 | * Gracefully accepting constructive criticism 60 | * Focusing on what is best for the community 61 | * Showing empathy towards other community members 62 | 63 | Examples of unacceptable behavior by participants include: 64 | 65 | * The use of sexualized language or imagery and unwelcome sexual attention or 66 | advances 67 | * Trolling, insulting/derogatory comments, and personal or political attacks 68 | * Public or private harassment 69 | * Publishing others' private information, such as a physical or electronic 70 | address, without explicit permission 71 | * Other conduct which could reasonably be considered inappropriate in a 72 | professional setting 73 | 74 | ### Our Responsibilities 75 | 76 | Project maintainers are responsible for clarifying the standards of acceptable 77 | behavior and are expected to take appropriate and fair corrective action in 78 | response to any instances of unacceptable behavior. 79 | 80 | Project maintainers have the right and responsibility to remove, edit, or 81 | reject comments, commits, code, wiki edits, issues, and other contributions 82 | that are not aligned to this Code of Conduct, or to ban temporarily or 83 | permanently any contributor for other behaviors that they deem inappropriate, 84 | threatening, offensive, or harmful. 85 | 86 | ### Scope 87 | 88 | This Code of Conduct applies both within project spaces and in public spaces 89 | when an individual is representing the project or its community. Examples of 90 | representing a project or community include using an official project e-mail 91 | address, posting via an official social media account, or acting as an appointed 92 | representative at an online or offline event. Representation of a project may be 93 | further defined and clarified by project maintainers. 94 | 95 | ### Enforcement 96 | 97 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 98 | reported by contacting the project team at rexdivakar@hotmail.com. All 99 | complaints will be reviewed and investigated and will result in a response that 100 | is deemed necessary and appropriate to the circumstances. The project team is 101 | obligated to maintain confidentiality with regard to the reporter of an incident. 102 | Further details of specific enforcement policies may be posted separately. 103 | 104 | Project maintainers who do not follow or enforce the Code of Conduct in good 105 | faith may face temporary or permanent repercussions as determined by other 106 | members of the project's leadership. 107 | 108 | ### Attribution 109 | 110 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 111 | available at [http://contributor-covenant.org/version/1/4][version] 112 | 113 | [homepage]: http://contributor-covenant.org 114 | [version]: http://contributor-covenant.org/version/1/4/ 115 | [1]:https://github.com/rexdivakar/Notifly/blob/main/CODE_OF_CONDUCT.md 116 | -------------------------------------------------------------------------------- /.others/telegram.py.bak: -------------------------------------------------------------------------------- 1 | """ Telegram Wrapper""""" 2 | 3 | import json 4 | import os 5 | import requests 6 | 7 | 8 | class BotHandler: 9 | def __init__(self, token, dir_download= './downloads'): 10 | self.token = token 11 | self.api_url = "https://api.telegram.org/bot{}/".format(token) 12 | self.file_url = "https://api.telegram.org/file/bot{}/".format(token) 13 | self.dirDownloads = dir_download 14 | 15 | # url = "https://api.telegram.org/bot/" 16 | 17 | def get_updates(self, offset=0, timeout=10): 18 | method = 'getUpdates' 19 | params = {'timeout': timeout, 'offset': offset} 20 | resp = requests.get(self.api_url + method, params) 21 | try: 22 | return resp.json()['result'] 23 | except TimeoutError as tm_err: 24 | print(tm_err) 25 | exit(1) 26 | 27 | def chat_id_response(self): 28 | try: 29 | fetch_updates = self.get_updates() 30 | return fetch_updates[0]['message']['chat']['id'] 31 | except TimeoutError as tm_err: 32 | print(tm_err) 33 | exit(1) 34 | 35 | def send_message(self, msg, notification=False): 36 | try: 37 | method = 'sendMessage' 38 | params = {'chat_id': self.chat_id_response(), 'text': msg, 39 | 'parse_mode': 'HTML', 'disable_notification': notification} 40 | return requests.post(self.api_url + method, params) 41 | except IndexError as err: 42 | print('Time out error') 43 | exit(1) 44 | 45 | def send_image(self, img_path): 46 | method = 'sendPhoto?' + 'chat_id=' + str(self.chat_id_response()) 47 | if img_path[-4:] in ['.jpg', '.png']: 48 | pass 49 | else: 50 | print('Invalid File Format, please use .jpg or .png format') 51 | exit(1) 52 | try: 53 | files = {'photo': open(img_path, 'rb')} 54 | return requests.post(self.api_url + method, files = files) 55 | except FileNotFoundError as fl_err: 56 | print(fl_err) 57 | exit(1) 58 | 59 | def send_document(self, file_path): 60 | method = 'sendDocument?' + 'chat_id=' + str(self.chat_id_response()) 61 | try: 62 | files = {'document': open(file_path, 'rb')} 63 | return requests.post(self.api_url + method, files = files) 64 | except FileNotFoundError as fn_err: 65 | print(fn_err) 66 | exit(1) 67 | except TimeoutError as tm_err: 68 | print(tm_err) 69 | exit(1) 70 | 71 | def download_files(self): 72 | fetch_updates = self.get_updates() 73 | for update in fetch_updates: 74 | file_name = '' 75 | media = ['document', 'photo', 'video', 'voice'] 76 | # Check if update message contains any of the media keys from above 77 | intersection = list(set(update['message'].keys()) & set(media)) 78 | if intersection: 79 | media_key = intersection[0] 80 | 81 | # Determine file_id. For photos multiple versions exist. Use the last one. 82 | if media_key == 'photo': 83 | file_id = update['message'][media_key][-1]['file_id'] 84 | else: 85 | file_id = update['message'][media_key]['file_id'] 86 | 87 | if media_key == 'document': 88 | # In a document, it's possible to use the original name. 89 | file_name = update['message'][media_key]['file_name'] 90 | self.get_file(file_id, file_name) 91 | 92 | def get_file(self, file_id, filename=''): 93 | """"Follow the getFile endpoint and download the file by file_id. 94 | A fileName can be given, else a file_unique_id gibberish name will be used. 95 | See also: https://core.telegram.org/bots/api#getfile 96 | """ 97 | method = 'getFile?' + 'file_id=' + str(file_id) 98 | res = requests.post(self.api_url + method, file_id) 99 | try: 100 | file_path = res.json()['result']['file_path'] 101 | # Determine the fileName. Use modified file_path if none given. 102 | if not filename: 103 | filename = file_path[file_path.rfind('/') + 1:] 104 | except (KeyError, ValueError): 105 | return "500 - Failed parsing the file link from API response." 106 | 107 | if not os.path.exists(self.dirDownloads): 108 | os.mkdir(self.dirDownloads) 109 | 110 | local_path = os.path.join(self.dirDownloads, filename) 111 | 112 | # Download file as stream. 113 | res = requests.get(self.file_url + file_path, stream=True) 114 | if res.status_code == 200: 115 | try: 116 | with open(local_path, 'wb') as f: 117 | for chunk in res: 118 | f.write(chunk) 119 | except IOError: 120 | pass 121 | return '200 - {} written.'.format(local_path) 122 | else: 123 | return '404 - Error accessing {}'.format(file_path) 124 | 125 | def session_dump(self): 126 | resp = self.get_updates() 127 | try: 128 | if not os.path.exists(self.dirDownloads): 129 | os.mkdir(self.dirDownloads) 130 | local_path = os.path.join(self.dirDownloads, 'session_dump.json') 131 | 132 | with open(local_path, 'w+', encoding='utf-8') as outfile: 133 | json.dump(resp, outfile) 134 | except IOError as io_err: 135 | print(io_err) 136 | exit(1) 137 | -------------------------------------------------------------------------------- /notifly/telegram.py: -------------------------------------------------------------------------------- 1 | """ Telegram Wrapper""" 2 | 3 | import json 4 | import os 5 | import sys 6 | import requests 7 | from notifly.tf_notifier import TfNotifier 8 | 9 | 10 | class Notifier(TfNotifier): 11 | def __init__(self, token, dir_download= './downloads'): 12 | """ 13 | Initialize the telegram client using tokens to access the HTTP API 14 | 15 | Args: 16 | token (string): API token [Mandatory]. 17 | Args: 18 | dir_download (string): -> Storage location for dumping payload [default = "./downloads"] 19 | """ 20 | self.token = token 21 | self.api_url = "https://api.telegram.org/bot{}/".format(token) 22 | self.file_url = "https://api.telegram.org/file/bot{}/".format(token) 23 | self.dirDownloads = dir_download 24 | 25 | def __get_updates(self, offset=0, timeout=10): 26 | """ 27 | Get the latest updates as json file 28 | 29 | Args: 30 | offset (int): default value 0 31 | timeout (int): Timeout for the request post (default value 10) 32 | Returns: 33 | The json results 34 | Raises: 35 | TimeoutError 36 | """ 37 | method = 'getUpdates' 38 | params = {'timeout': timeout, 'offset': offset} 39 | resp = requests.get(self.api_url + method, params) 40 | try: 41 | return resp.json()['result'] 42 | except KeyError: 43 | print('TimeoutError') 44 | sys.exit(1) 45 | except requests.exceptions.ConnectionError as err: 46 | print('HTTPS Connection Error - Unable to reach the destination') 47 | 48 | def __chat_id_response(self) -> int: 49 | """ 50 | Fetches the latest chat id from the client 51 | 52 | Returns: 53 | The latest chat-id of the client 54 | Raises: 55 | TimeoutError 56 | """ 57 | try: 58 | fetch_updates = self.__get_updates() 59 | return fetch_updates[0]['message']['chat']['id'] 60 | except TimeoutError as tm_err: 61 | print(tm_err) 62 | sys.exit(1) 63 | 64 | def send_message(self, msg, notification=False) -> object: 65 | """ 66 | Function to send message 67 | 68 | Args: 69 | msg (string): Enter the message to post 70 | notification (bool): Disable_Notification (default=False) 71 | Returns: 72 | The status_code of the post operation (send_message) 73 | Raises: 74 | IndexError 75 | """ 76 | try: 77 | method = 'sendMessage' 78 | params = {'chat_id': self.__chat_id_response(), 'text': msg, 79 | 'parse_mode': 'HTML', 'disable_notification': notification} 80 | return requests.post(self.api_url + method, params) 81 | except IndexError: 82 | print('Time out error') 83 | sys.exit(1) 84 | except requests.exceptions.ConnectionError as err: 85 | print('HTTPS Connection Error - Unable to reach the destination') 86 | sys.exit(1) 87 | 88 | def send_image(self, img_path) -> object: 89 | """ 90 | Function to send image via telegram 91 | 92 | Args: 93 | img_path (basestring): Enter the file_path to send the image file 94 | Returns: 95 | The status_code of the post operation (send_image) 96 | Raises: 97 | FileNotFoundError, InvalidFormatError 98 | """ 99 | method = 'sendPhoto?' + 'chat_id=' + str(self.__chat_id_response()) 100 | if img_path[-4:] not in ['.jpg', '.png']: 101 | print('Invalid File Format, please use .jpg or .png format') 102 | sys.exit(1) 103 | try: 104 | files = {'photo': open(img_path, 'rb')} 105 | return requests.post(self.api_url + method, files = files) 106 | except FileNotFoundError as fl_err: 107 | print(fl_err) 108 | sys.exit(1) 109 | except requests.exceptions.ConnectionError as err: 110 | print('HTTPS Connection Error - Unable to reach the destination') 111 | sys.exit(1) 112 | 113 | def send_file(self, file_path) -> object: 114 | """ 115 | Function to send documents via telegram 116 | 117 | Args: 118 | file_path (basestring): Enter the file_path to send the image file 119 | Returns: 120 | The status_code of the post operation (send_document) 121 | Raises: 122 | FileNotFoundError, TimeoutError 123 | """ 124 | method = 'sendDocument?' + 'chat_id=' + str(self.__chat_id_response()) 125 | try: 126 | files = {'document': open(file_path, 'rb')} 127 | return requests.post(self.api_url + method, files = files) 128 | except FileNotFoundError as fn_err: 129 | print(fn_err) 130 | sys.exit(1) 131 | except TimeoutError as tm_err: 132 | print(tm_err) 133 | sys.exit(1) 134 | except requests.exceptions.ConnectionError as err: 135 | print('HTTPS Connection Error - Unable to reach the destination') 136 | sys.exit(1) 137 | 138 | def session_dump(self) -> json: 139 | """ 140 | Function to Dump all the data from the telegram client during the current session 141 | 142 | Returns: 143 | Dumps the session details in the form of json file. 144 | Raises: 145 | IOError 146 | """ 147 | resp = self.__get_updates() 148 | try: 149 | if not os.path.exists(self.dirDownloads): 150 | os.mkdir(self.dirDownloads) 151 | local_path = os.path.join(self.dirDownloads, 'session_dump.json') 152 | 153 | with open(local_path, 'w+', encoding='utf-8') as outfile: 154 | json.dump(resp, outfile) 155 | except IOError as io_err: 156 | print(io_err) 157 | sys.exit(1) 158 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | notifly API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Package notifly

23 |
24 |
25 |

Notifly Documentation

26 |

Discord: (object): Discord wrapper to send message, images to the channel using Webhooks 27 | Slack (object): Slack wrapper to send message, images to the channel using API 28 | Telegram (object): Telegram Web Client wrapper to send images, files over the bot using API

29 |
30 | 31 | Expand source code 32 | 33 |
"""
34 | Notifly Documentation
35 | 
36 | Discord: (object): Discord wrapper to send message, images to the channel using Webhooks
37 | Slack (object): Slack wrapper to send message, images to the channel using API
38 | Telegram (object): Telegram Web Client wrapper to send images, files over the bot using API
39 | """
40 |
41 |
42 |
43 |

Sub-modules

44 |
45 |
notifly.discord
46 |
47 |

Discord API webhooks wrapper

48 |
49 |
notifly.slack
50 |
51 |

Slack-API call wrapper

52 |
53 |
notifly.telegram
54 |
55 |

Telegram Wrapper

56 |
57 |
notifly.tf_notifier
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 86 |
87 | 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notifly 2 | ### [Code Usage](https://rexdivakar.github.io/Notifly/) 3 | ![PyPI](https://img.shields.io/pypi/v/notifly?logo=github&style=for-the-badge) 4 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/notifly?color=green&style=for-the-badge&logo=github) 5 | ![Discord](https://img.shields.io/discord/760088481224851476?label=DISCORD&logo=discord&logoColor=green&style=for-the-badge) 6 | 7 | 8 |

9 | Logo 10 | 11 | ## Table of Contents 12 | * [About the package](#about-the-package) 13 | * [Built With](#built-with) 14 | * [Prerequisites for install](#prerequisites) 15 | * [Install the package](#install-the-package) 16 | * [Working of the tool](#working-of-the-tool) 17 | * [Telegram](#telegram) 18 | * [Discord](#discord) 19 | * [Slack](#slack) 20 | * [Contributing](#contributing) 21 | 22 | ## About the package 23 | Simple Bots to push notifications during an event trigger.
24 | 25 | [!["Telegram"](https://img.shields.io/badge/%20Telegram-%20.svg?longCache=true&logo=telegram&colorB=blue)](https://telegram.org/) 26 | wrapper to send messages, images, files over the bot using API.
27 | [!["Discord"](https://img.shields.io/badge/%20Discord-%20.svg?longCache=true&logo=discord&colorB=lightblue)](https://discord.com/) 28 | wrapper to send message, images, files to the channel using Webhooks.
29 | [!["Slack"](https://img.shields.io/badge/%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://slack.com) 30 | wrapper to send message, images, files to the channel using API. 31 | 32 | ### Built With 33 | * [Python 3][1] 34 | 35 | ## Prerequisites 36 | * Python
37 | It is preinstalled in Ubuntu 20.04. To check the version use command : 38 | ``` 39 | python3 --version 40 | ``` 41 | If it is not preinstalled for some reason, proceed [here][4] and download as per requirement. 42 | 43 | Run the following command in terminal to download the required packags for running the tool locally : 44 | * Using requirements file : 45 | ``` 46 | pip3 install -r requirements.txt 47 | ``` 48 | * Directly download packages: 49 | ``` 50 | pip3 install requests==2.24.0 51 | pip3 install matplotlib==3.2.2 52 | pip3 install slackclient==2.9.3 53 | ``` 54 | 55 | ## Install the package 56 | Run the following terminal commands to install the package on the given distros. 57 | * Termux : 58 | ``` 59 | pkg install python3 60 | ``` 61 | ``` 62 | pip3 install notifly 63 | ``` 64 | * Ubuntu/Debian 65 | ``` 66 | sudo apt install python3-pip 67 | ``` 68 | ``` 69 | pip3 install notifly 70 | ``` 71 | * Arch 72 | ``` 73 | sudo pacman -S python3-pip 74 | ``` 75 | ``` 76 | pip3 install notifly 77 | ``` 78 | ***This may take a while depending on the network speed.*** 79 | 80 | ## Working of the tool 81 | ### Telegram 82 | To see how the tool works, 83 | 1. Create the [telegram bot][2]. 84 | 2. Getting the bot API token 85 | 1. Search and go to ```_@Botfather_``` . 86 | 1. Message ```/mybots``` . 87 | 1. Select the bot. 88 | 1. Select the _API token_ displayed in message. 89 | 1. Copy and use in sample code. 90 | ```python 91 | from notifly import telegram #import the package 92 | x = telegram.Notifier('bot API token') #create object of class Notifier 93 | x.send_message('message') #send message 94 | x.send_image("image address") #send image(.jpg or .png format) 95 | x.send_file("file address") #send document 96 | x.session_dump() #creates folder named 'downloads' in local folder, downloads/saves message,chat details for current session in 'sessio_dump.json' file 97 | ``` 98 | 3. Run sample code. 99 | ### Discord 100 | To see how the tool works, 101 | 1. Create server. 102 | 2. Create and copy server [webhook][5] and use in sample code. 103 | ```python 104 | from notifly import discord 105 | x = discord.Notifier(r'webhook') #create object of class Notifier 106 | x.send_message('message') #send message 107 | x.send_file("file address") #send file 108 | x.send_file("image address") #send image 109 | ``` 110 | 3. Run sample code. 111 | ### Slack 112 | To see how the tool works, 113 | 1. Create app. Follow these steps, 114 | 1. Go [here][6]. 115 | 2. Go to ```Create an App``` . 116 | 3. Enter _App Name_ and select workspace. Click ```Create App```. 117 | 4. Under **Add features and functionality** select ```Incoming Webhooks``` and **Activate Incoming Webhooks**. 118 | 5. Scroll down, select ```Add New Webhook to Workspace``` and select a channel from the drop down.This channel name is used as an argument in the sample code. Click ```Allow```. 119 | 6. Select **OAuth & Permissions** from left-sidebar. 120 | 7. Under **Scopes** > **Bot Token Scopes** click ```Add an OAuth Scope``` and add the following scopes, 121 |
```chat:write```   ```chat:write.public```   ```files:write```   ```users:write``` 122 | 8. Scroll up, under **OAuth Tokens for Your Team** copy the *Bot User OAuth Access Token* to use in sample code. 123 | 9. Click ```Reinstall to Workspace```, select channel and click ```Allow```. 124 | 2. Write sample code. 125 | ```python 126 | from notifly import slack 127 | x= slack.Notifier('token', channel='channel-name') #create object of class Notiflier 128 | x.send_message('message') #send message 129 | x.send_file("image or file address") #send image/file 130 | ``` 131 | 3. Run sample code. 132 | 133 | ### Tensorflow Integration 134 | Plug and play feature for your tensorflow callbacks 135 | ```python 136 | # create your notifier using above methods 137 | from notifly import discord 138 | notifier = discord.Notifier(r'webhook') 139 | class MyNotifierCallback: 140 | 141 | @notifier.notify_on_epoch_begin(epoch_interval=1, graph_interval=1, hardware_stats_interval=1) 142 | def on_epoch_begin(self, epoch, logs=None): 143 | pass 144 | 145 | @notifier.notify_on_epoch_end(epoch_interval=1, graph_interval=1, hardware_stats_interval=1) 146 | def on_epoch_end(self, epoch, logs=None): 147 | pass 148 | 149 | @notifier.notify_on_train_begin() 150 | def on_train_begin(self, logs=None): 151 | pass 152 | 153 | @notifier.notify_on_train_end() 154 | def on_train_end(self, logs=None): 155 | pass 156 | 157 | model.fit(callbacks=[MyNotifierCallback()]) 158 | ``` 159 | ## Learn more about Notifly ✨ 160 | Read the [wiki pages](https://github.com/rexdivakar/Notifly/wiki) which has all the above steps in great detail with some examples as well 🤩🎉. 161 | 162 | ## Contributing 163 | 1. Fork the Project 164 | 1. Create your Feature Branch 165 | >git checkout -b feature/mybranch 166 | 1. Commit your Changes 167 | >git commit -m 'Add something' 168 | 1. Push to the Branch 169 | >git push origin feature/mybranch 170 | 1. Open a Pull Request

171 | Follow the given commands or use the amazing ***GitHub GUI***
172 | **Happy Contributing** 173 | 174 | [contributors-shield]: https://img.shields.io/github/contributors/rexdivakar/Telegram-Notifly.svg?style=flat-square 175 | [contributors-url]: https://github.com/rexdivakar/Telegram-Notifly/graphs/contributors 176 | [forks-shield]: https://img.shields.io/github/forks/rexdivakar/Telegram-Notifly.svg?style=flat-square 177 | [forks-url]: https://github.com/rexdivakar/Telegram-Notifly/network/members 178 | [stars-shield]: https://img.shields.io/github/stars/rexdivakar/Telegram-Notifly.svg?style=flat-square 179 | [stars-url]: https://github.com/rexdivakar/Telegram-Notifly/stargazers 180 | [issues-shield]: https://img.shields.io/github/issues/rexdivakar/Telegram-Notifly.svg?style=flat-square 181 | [issues-url]: https://github.com/rexdivakar/Telegram-Notifly/issues 182 | [license-shield]: https://img.shields.io/github/license/rexdivakar/Telegram-Notifly.svg?style=flat-square 183 | [license-url]: https://github.com/rexdivakar/Telegram-Notifly/blob/master/LICENSE.txt 184 | [1]:https://www.python.org/ 185 | [2]:https://telegram.org/blog/bot-revolution 186 | [4]:https://www.python.org/downloads/ 187 | [5]:https://discordjs.guide/popular-topics/webhooks.html#fetching-all-webhooks-of-a-guild 188 | [6]:https://api.slack.com/ 189 | -------------------------------------------------------------------------------- /notifly/tf_notifier.py: -------------------------------------------------------------------------------- 1 | from notifly import gpu_stats 2 | import inspect 3 | import matplotlib.pyplot as plt 4 | import copy 5 | import psutil 6 | 7 | 8 | class TfNotifier: 9 | 10 | def send_message(self, message): 11 | return 12 | 13 | def send_file(self, file_path): 14 | return 15 | 16 | @staticmethod 17 | def get_hardware_stats(): 18 | """ 19 | Fetches the hardware stats on regular intervals 20 | 21 | Returns: 22 | The parameters of CPU, GPU, RAM 23 | Raises: 24 | Exception 25 | """ 26 | try: 27 | cpu_usage = psutil.cpu_percent(interval = 0.2) 28 | mem_stats = psutil.virtual_memory() 29 | ram_usage = round((mem_stats.used / mem_stats.total) * 100, 2) 30 | except Exception as e: 31 | print('Unable to find system parameters', e) 32 | 33 | x = gpu_stats.gpu() 34 | if x is None: 35 | return f"CPU Usage: {cpu_usage}%, RAM Usage: {ram_usage}%" 36 | 37 | gpu_util = x[2] 38 | tot_v_ram = x[3] 39 | v_ram_used = x[4] 40 | unused_vram = x[5] 41 | driver_ver = x[6] 42 | gpu_name = x[7] 43 | gpu_temp = x[11].strip() 44 | return f"CPU Usage: {cpu_usage}%, RAM Usage: {ram_usage}%, GPU Usage: {gpu_util}%, GPU Temp: {gpu_temp}," \ 45 | f" GPU Memory: {v_ram_used} MB, GPU Unused Memory: {unused_vram} MB" 46 | 47 | @staticmethod 48 | def plot_graph(history, current_epoch_logs): 49 | """ 50 | Plots the graphs on runtime 51 | 52 | Args: 53 | history (dict): gets the logs of the tensorflow model.train() 54 | current_epoch_logs (int): Fetches the epochs on runtime event 55 | Returns: 56 | Plots the graph on runtime 57 | """ 58 | 59 | # merge history with current epoch logs 60 | for key, value in current_epoch_logs.items(): 61 | if key not in history: 62 | history[key] = [value] 63 | else: 64 | history[key].append(value) 65 | 66 | plt.figure() 67 | for key, value in history.items(): 68 | if len(value) != 1: 69 | plt.plot(range(1, len(value) + 1), value, label = str(key)) 70 | else: 71 | plt.scatter(range(1, len(value) + 1), value, label = str(key)) 72 | plt.title("Training History") 73 | 74 | plt.xlabel('Epochs') 75 | plt.legend() 76 | plt.pause(1e-13) 77 | file_path = 'fig.png' 78 | plt.savefig(file_path, bbox_inches = 'tight') 79 | plt.close() 80 | return file_path 81 | 82 | def notify_on_train_begin(self): 83 | """ 84 | Decorator which runs on beginning of the training model. 85 | 86 | Returns: 87 | Updated function 88 | """ 89 | 90 | def inner(func_to_call): 91 | def wrapper(*args, **kwargs): 92 | # get parameter values 93 | parameter_values = list(args) 94 | for i in kwargs.values(): 95 | parameter_values.append(i) 96 | 97 | # get arguments names from the function signature 98 | sig = inspect.signature(func_to_call) 99 | parameter_names = sig.parameters.keys() 100 | 101 | # parameter mapping with names to values 102 | parameter_mapping = dict(zip(parameter_names, parameter_values)) 103 | 104 | # get starting logs 105 | starting_logs = parameter_mapping.get('logs') 106 | 107 | self.send_message(f':muscle: training started, got log keys: {starting_logs}') 108 | 109 | return func_to_call(*args, **kwargs) 110 | 111 | return wrapper 112 | 113 | return inner 114 | 115 | def notify_on_train_end(self): 116 | """ 117 | Decorator which runs on end of each iteration during training model. 118 | 119 | Returns: 120 | Updated function 121 | """ 122 | def inner(func_to_call): 123 | def wrapper(*args, **kwargs): 124 | # get parameter values 125 | parameter_values = list(args) 126 | for i in kwargs.values(): 127 | parameter_values.append(i) 128 | 129 | # get arguments names from the function signature 130 | sig = inspect.signature(func_to_call) 131 | parameter_names = sig.parameters.keys() 132 | 133 | # parameter mapping with names to values 134 | parameter_mapping = dict(zip(parameter_names, parameter_values)) 135 | 136 | # get starting logs 137 | ending_logs = parameter_mapping.get('logs') 138 | 139 | self.send_message(f':tada: training ended, got log keys: {ending_logs}') 140 | 141 | return func_to_call(*args, **kwargs) 142 | 143 | return wrapper 144 | 145 | return inner 146 | 147 | def notify_on_epoch_begin(self, epoch_interval, graph_interval, hardware_stats_interval): 148 | """ 149 | Decorator which runs on beginning of the training model. 150 | 151 | Returns: 152 | Updated function 153 | """ 154 | def inner(func_to_call): 155 | def wrapper(*args, **kwargs): 156 | 157 | # get parameter values 158 | parameter_values = list(args) 159 | for i in kwargs.values(): 160 | parameter_values.append(i) 161 | 162 | # get arguments names from the function signature 163 | sig = inspect.signature(func_to_call) 164 | parameter_names = sig.parameters.keys() 165 | 166 | # parameter mapping with names to values 167 | parameter_mapping = dict(zip(parameter_names, parameter_values)) 168 | 169 | # get model instance 170 | model_instance = parameter_mapping.get('self').model 171 | 172 | # get current epoch because epoch starts from 0 173 | current_epoch = parameter_mapping.get('epoch') + 1 174 | 175 | # get current epoch logs 176 | current_epoch_logs = parameter_mapping.get('logs') 177 | 178 | # notify if current_epoch is divisible by epoch_interval 179 | if current_epoch % epoch_interval == 0: 180 | message = f"epoch: {current_epoch} started, got log keys:" 181 | for k, v in current_epoch_logs.items(): 182 | message += " {}: {:.4f} ".format(k, v) 183 | self.send_message(message) 184 | 185 | # notify if current_epoch is divisible by hardware_stats_interval 186 | if current_epoch % hardware_stats_interval == 0: 187 | hardware_stats = TfNotifier.get_hardware_stats() 188 | self.send_message(hardware_stats) 189 | 190 | # notify graph if current_epoch is divisible by graph_interval 191 | if current_epoch % graph_interval == 0: 192 | history_copy = copy.deepcopy(model_instance.history.history) 193 | file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs) 194 | # TODO: change this function call 195 | self.send_file(file_path) 196 | 197 | return func_to_call(*args, **kwargs) 198 | 199 | return wrapper 200 | 201 | return inner 202 | 203 | def notify_on_epoch_end(self, epoch_interval, graph_interval, hardware_stats_interval): 204 | """ 205 | Decorator which runs on end of the training model. 206 | 207 | Returns: 208 | Updated function 209 | """ 210 | def inner(func_to_call): 211 | def wrapper(*args, **kwargs): 212 | 213 | # get parameter values 214 | parameter_values = list(args) 215 | for i in kwargs.values(): 216 | parameter_values.append(i) 217 | 218 | # get arguments names from the function signature 219 | sig = inspect.signature(func_to_call) 220 | parameter_names = sig.parameters.keys() 221 | 222 | # parameter mapping with names to values 223 | parameter_mapping = dict(zip(parameter_names, parameter_values)) 224 | 225 | # get model instance 226 | model_instance = parameter_mapping.get('self').model 227 | 228 | # get current epoch because epoch starts from 0 229 | current_epoch = parameter_mapping.get('epoch') + 1 230 | 231 | # get current epoch logs 232 | current_epoch_logs = parameter_mapping.get('logs') 233 | 234 | # notify if current_epoch is divisible by epoch_interval 235 | if current_epoch % epoch_interval == 0: 236 | message = f"epoch: {current_epoch} ended, got log keys:" 237 | for k, v in current_epoch_logs.items(): 238 | message += " {}: {:.4f} ".format(k, v) 239 | self.send_message(message) 240 | 241 | # notify if current_epoch is divisible by hardware_stats_interval 242 | if current_epoch % hardware_stats_interval == 0: 243 | hardware_stats = TfNotifier.get_hardware_stats() 244 | self.send_message(hardware_stats) 245 | 246 | # notify graph if current_epoch is divisible by graph_interval 247 | if current_epoch % graph_interval == 0: 248 | history_copy = copy.deepcopy(model_instance.history.history) 249 | file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs) 250 | # TODO: change this function call 251 | self.send_file(file_path) 252 | 253 | return func_to_call(*args, **kwargs) 254 | 255 | return wrapper 256 | 257 | return inner 258 | -------------------------------------------------------------------------------- /docs/slack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | notifly.slack API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 |
21 |
22 |

Module notifly.slack

23 |
24 |
25 |

Slack-API call wrapper

26 |
27 | 28 | Expand source code 29 | 30 |
"""Slack-API call wrapper"""
 31 | 
 32 | 
 33 | import slack
 34 | from slack.errors import SlackApiError
 35 | 
 36 | 
 37 | class Notifier:
 38 |     def __init__(self, token, channel = 'general'):
 39 |         """
 40 |         Initialize the slack webclient instance using API.
 41 | 
 42 |         Args:
 43 |             token (string): API token [Mandatory].
 44 |             channel (string): Post's the notification to it's corresponding channel [default = #general].
 45 |         Returns:
 46 |             The output of slack API_test which verify the Authentication protocol.
 47 |         Raises:
 48 |             SlackApiError.
 49 |         """
 50 |         self.__client = slack.WebClient(token = token)
 51 |         self.__channel = channel
 52 | 
 53 |         try:
 54 |             self.__client.api_test()['ok']
 55 |         except SlackApiError as api_err:
 56 |             print(f"Got an error: {api_err.response['error']}")
 57 |             exit(1)
 58 | 
 59 |     def send_message(self, msg) -> object:  #TODO  Add unicode check
 60 |         """
 61 |         Function to post message to the slack channel.
 62 | 
 63 |         Args:
 64 |             msg (string): Enter your message to post.
 65 |         Returns:
 66 |             Outputs the response of message on post operation.
 67 |         Raises:
 68 |             SlackApiError
 69 |         """
 70 |         try:
 71 |             return self.__client.chat_postMessage(channel = self.__channel, text = msg)
 72 |         except SlackApiError as api_err:
 73 |             print(f"Got an error: {api_err.response['error']}")
 74 |             exit(1)
 75 | 
 76 |     def send_file(self, file_path) -> object:
 77 |         """
 78 |         Function to post an file to the slack channel.
 79 | 
 80 |         Args:
 81 |             file_path (string): Enter the path of the file to be sent.
 82 |         Returns:
 83 |             Outputs the response of message on post operation.
 84 |         Raises:
 85 |                 FileNotFoundError
 86 |         """
 87 |         try:
 88 |             return self.__client.files_upload(file = file_path,  channels = self.__channel)
 89 |         except FileNotFoundError as fl_err:
 90 |             print(fl_err)
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |

Classes

101 |
102 |
103 | class Notifier 104 | (token, channel='general') 105 |
106 |
107 |

Initialize the slack webclient instance using API.

108 |

Args

109 |
110 |
token : string
111 |
API token [Mandatory].
112 |
channel : string
113 |
Post's the notification to it's corresponding channel [default = #general].
114 |
115 |

Returns

116 |

The output of slack API_test which verify the Authentication protocol.

117 |

Raises

118 |

SlackApiError.

119 |
120 | 121 | Expand source code 122 | 123 |
class Notifier:
124 |     def __init__(self, token, channel = 'general'):
125 |         """
126 |         Initialize the slack webclient instance using API.
127 | 
128 |         Args:
129 |             token (string): API token [Mandatory].
130 |             channel (string): Post's the notification to it's corresponding channel [default = #general].
131 |         Returns:
132 |             The output of slack API_test which verify the Authentication protocol.
133 |         Raises:
134 |             SlackApiError.
135 |         """
136 |         self.__client = slack.WebClient(token = token)
137 |         self.__channel = channel
138 | 
139 |         try:
140 |             self.__client.api_test()['ok']
141 |         except SlackApiError as api_err:
142 |             print(f"Got an error: {api_err.response['error']}")
143 |             exit(1)
144 | 
145 |     def send_message(self, msg) -> object:  #TODO  Add unicode check
146 |         """
147 |         Function to post message to the slack channel.
148 | 
149 |         Args:
150 |             msg (string): Enter your message to post.
151 |         Returns:
152 |             Outputs the response of message on post operation.
153 |         Raises:
154 |             SlackApiError
155 |         """
156 |         try:
157 |             return self.__client.chat_postMessage(channel = self.__channel, text = msg)
158 |         except SlackApiError as api_err:
159 |             print(f"Got an error: {api_err.response['error']}")
160 |             exit(1)
161 | 
162 |     def send_file(self, file_path) -> object:
163 |         """
164 |         Function to post an file to the slack channel.
165 | 
166 |         Args:
167 |             file_path (string): Enter the path of the file to be sent.
168 |         Returns:
169 |             Outputs the response of message on post operation.
170 |         Raises:
171 |                 FileNotFoundError
172 |         """
173 |         try:
174 |             return self.__client.files_upload(file = file_path,  channels = self.__channel)
175 |         except FileNotFoundError as fl_err:
176 |             print(fl_err)
177 |
178 |

Methods

179 |
180 |
181 | def send_file(self, file_path) ‑> object 182 |
183 |
184 |

Function to post an file to the slack channel.

185 |

Args

186 |
187 |
file_path : string
188 |
Enter the path of the file to be sent.
189 |
190 |

Returns

191 |

Outputs the response of message on post operation.

192 |

Raises

193 |

FileNotFoundError

194 |
195 | 196 | Expand source code 197 | 198 |
def send_file(self, file_path) -> object:
199 |     """
200 |     Function to post an file to the slack channel.
201 | 
202 |     Args:
203 |         file_path (string): Enter the path of the file to be sent.
204 |     Returns:
205 |         Outputs the response of message on post operation.
206 |     Raises:
207 |             FileNotFoundError
208 |     """
209 |     try:
210 |         return self.__client.files_upload(file = file_path,  channels = self.__channel)
211 |     except FileNotFoundError as fl_err:
212 |         print(fl_err)
213 |
214 |
215 |
216 | def send_message(self, msg) ‑> object 217 |
218 |
219 |

Function to post message to the slack channel.

220 |

Args

221 |
222 |
msg : string
223 |
Enter your message to post.
224 |
225 |

Returns

226 |

Outputs the response of message on post operation.

227 |

Raises

228 |

SlackApiError

229 |
230 | 231 | Expand source code 232 | 233 |
def send_message(self, msg) -> object:  #TODO  Add unicode check
234 |     """
235 |     Function to post message to the slack channel.
236 | 
237 |     Args:
238 |         msg (string): Enter your message to post.
239 |     Returns:
240 |         Outputs the response of message on post operation.
241 |     Raises:
242 |         SlackApiError
243 |     """
244 |     try:
245 |         return self.__client.chat_postMessage(channel = self.__channel, text = msg)
246 |     except SlackApiError as api_err:
247 |         print(f"Got an error: {api_err.response['error']}")
248 |         exit(1)
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 | 280 |
281 | 284 | 285 | -------------------------------------------------------------------------------- /docs/discord.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | notifly.discord API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module notifly.discord

23 |
24 |
25 |

Discord API webhooks wrapper

26 |
27 | 28 | Expand source code 29 | 30 |
"""Discord API webhooks wrapper"""
 31 | 
 32 | import requests
 33 | from requests import exceptions
 34 | 
 35 | 
 36 | class Notifier:
 37 | 
 38 |     class __AuthError(Exception):
 39 |         """
 40 |         Authentication Exception
 41 |         """
 42 |         pass
 43 | 
 44 |     def __init__(self, webhooks):
 45 |         """
 46 |         Initialize the Discord Webhook instance
 47 | 
 48 |         Args:
 49 |             webhooks (basestring): Discord webhook token [Mandatory]
 50 |         Returns:
 51 |             The response of webhook status
 52 |         Raises:
 53 |             Exception, AuthError, MissingSchema
 54 |         """
 55 |         self.__webhooks = webhooks
 56 |         self.payload = None
 57 | 
 58 |         try:
 59 |             if requests.get(self.__webhooks).status_code == 401:
 60 |                 raise Notifier.__AuthError('Invalid Webhook')
 61 |         except exceptions.ConnectionError as err:
 62 |             print(err)
 63 |         except Notifier.__AuthError as ty_err:
 64 |             print(ty_err)
 65 |             exit(1)
 66 |         except requests.models.MissingSchema as ms_err:
 67 |             print(ms_err)
 68 |             exit(1)
 69 | 
 70 |     def send_message(self, msg) -> object:
 71 |         """
 72 |         Function to post message to the discord channel
 73 | 
 74 |         Args:
 75 |             msg (string): Posts message to the discord channel [String] [UTF-8]
 76 |         Returns:
 77 |             The response to the message on post operation.
 78 |         Raises:
 79 |             ConnectionError
 80 |         """
 81 |         payload = {'content': str(msg)}
 82 |         try:
 83 |             return requests.post(url = self.__webhooks, data = payload)
 84 |         except exceptions.ConnectionError as cer:
 85 |             print(cer)
 86 |             exit(1)
 87 | 
 88 |     def send_file(self, file_path) -> object:
 89 |         """
 90 |         Function to post an image to the discord channel
 91 | 
 92 |         Args:
 93 |             file_path (string): Enter the path of the file to be sent
 94 |         Returns:
 95 |             The response to the message on post operation.
 96 |         Raises:
 97 |             FileNotFoundError, OverflowError
 98 |         """
 99 |         try:
100 |             self.payload = {'file': open(file_path, 'rb')}
101 |         except FileNotFoundError as fl_er:
102 |             print(fl_er)
103 |             exit(1)
104 |         try:
105 |             return requests.post(url = self.__webhooks, files = self.payload)
106 |         except OverflowError as err:
107 |             print('Size Overflow Error', err)
108 |             exit(1)
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |

Classes

119 |
120 |
121 | class Notifier 122 | (webhooks) 123 |
124 |
125 |

Initialize the Discord Webhook instance

126 |

Args

127 |
128 |
webhooks : basestring
129 |
Discord webhook token [Mandatory]
130 |
131 |

Returns

132 |

The response of webhook status

133 |

Raises

134 |

Exception, AuthError, MissingSchema

135 |
136 | 137 | Expand source code 138 | 139 |
class Notifier:
140 | 
141 |     class __AuthError(Exception):
142 |         """
143 |         Authentication Exception
144 |         """
145 |         pass
146 | 
147 |     def __init__(self, webhooks):
148 |         """
149 |         Initialize the Discord Webhook instance
150 | 
151 |         Args:
152 |             webhooks (basestring): Discord webhook token [Mandatory]
153 |         Returns:
154 |             The response of webhook status
155 |         Raises:
156 |             Exception, AuthError, MissingSchema
157 |         """
158 |         self.__webhooks = webhooks
159 |         self.payload = None
160 | 
161 |         try:
162 |             if requests.get(self.__webhooks).status_code == 401:
163 |                 raise Notifier.__AuthError('Invalid Webhook')
164 |         except exceptions.ConnectionError as err:
165 |             print(err)
166 |         except Notifier.__AuthError as ty_err:
167 |             print(ty_err)
168 |             exit(1)
169 |         except requests.models.MissingSchema as ms_err:
170 |             print(ms_err)
171 |             exit(1)
172 | 
173 |     def send_message(self, msg) -> object:
174 |         """
175 |         Function to post message to the discord channel
176 | 
177 |         Args:
178 |             msg (string): Posts message to the discord channel [String] [UTF-8]
179 |         Returns:
180 |             The response to the message on post operation.
181 |         Raises:
182 |             ConnectionError
183 |         """
184 |         payload = {'content': str(msg)}
185 |         try:
186 |             return requests.post(url = self.__webhooks, data = payload)
187 |         except exceptions.ConnectionError as cer:
188 |             print(cer)
189 |             exit(1)
190 | 
191 |     def send_file(self, file_path) -> object:
192 |         """
193 |         Function to post an image to the discord channel
194 | 
195 |         Args:
196 |             file_path (string): Enter the path of the file to be sent
197 |         Returns:
198 |             The response to the message on post operation.
199 |         Raises:
200 |             FileNotFoundError, OverflowError
201 |         """
202 |         try:
203 |             self.payload = {'file': open(file_path, 'rb')}
204 |         except FileNotFoundError as fl_er:
205 |             print(fl_er)
206 |             exit(1)
207 |         try:
208 |             return requests.post(url = self.__webhooks, files = self.payload)
209 |         except OverflowError as err:
210 |             print('Size Overflow Error', err)
211 |             exit(1)
212 |
213 |

Methods

214 |
215 |
216 | def send_file(self, file_path) ‑> object 217 |
218 |
219 |

Function to post an image to the discord channel

220 |

Args

221 |
222 |
file_path : string
223 |
Enter the path of the file to be sent
224 |
225 |

Returns

226 |

The response to the message on post operation.

227 |

Raises

228 |

FileNotFoundError, OverflowError

229 |
230 | 231 | Expand source code 232 | 233 |
def send_file(self, file_path) -> object:
234 |     """
235 |     Function to post an image to the discord channel
236 | 
237 |     Args:
238 |         file_path (string): Enter the path of the file to be sent
239 |     Returns:
240 |         The response to the message on post operation.
241 |     Raises:
242 |         FileNotFoundError, OverflowError
243 |     """
244 |     try:
245 |         self.payload = {'file': open(file_path, 'rb')}
246 |     except FileNotFoundError as fl_er:
247 |         print(fl_er)
248 |         exit(1)
249 |     try:
250 |         return requests.post(url = self.__webhooks, files = self.payload)
251 |     except OverflowError as err:
252 |         print('Size Overflow Error', err)
253 |         exit(1)
254 |
255 |
256 |
257 | def send_message(self, msg) ‑> object 258 |
259 |
260 |

Function to post message to the discord channel

261 |

Args

262 |
263 |
msg : string
264 |
Posts message to the discord channel [String] [UTF-8]
265 |
266 |

Returns

267 |

The response to the message on post operation.

268 |

Raises

269 |

ConnectionError

270 |
271 | 272 | Expand source code 273 | 274 |
def send_message(self, msg) -> object:
275 |     """
276 |     Function to post message to the discord channel
277 | 
278 |     Args:
279 |         msg (string): Posts message to the discord channel [String] [UTF-8]
280 |     Returns:
281 |         The response to the message on post operation.
282 |     Raises:
283 |         ConnectionError
284 |     """
285 |     payload = {'content': str(msg)}
286 |     try:
287 |         return requests.post(url = self.__webhooks, data = payload)
288 |     except exceptions.ConnectionError as cer:
289 |         print(cer)
290 |         exit(1)
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 | 322 |
323 | 326 | 327 | -------------------------------------------------------------------------------- /docs/telegram.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | notifly.telegram API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module notifly.telegram

23 |
24 |
25 |

Telegram Wrapper

26 |
27 | 28 | Expand source code 29 | 30 |
""" Telegram Wrapper"""
 31 | 
 32 | import json
 33 | import os
 34 | import requests
 35 | 
 36 | 
 37 | class BotHandler:
 38 |     def __init__(self, token, dir_download= './downloads'):
 39 |         """
 40 |         Initialize the telegram client using tokens to access the HTTP API
 41 | 
 42 |         Args:
 43 |             token (string): API token [Mandatory].
 44 |         Args:
 45 |             dir_download (string): -> Storage location for dumping payload [default = "./downloads"]
 46 |         """
 47 |         self.token = token
 48 |         self.api_url = "https://api.telegram.org/bot{}/".format(token)
 49 |         self.file_url = "https://api.telegram.org/file/bot{}/".format(token)
 50 |         self.dirDownloads = dir_download
 51 | 
 52 |     def __get_updates(self, offset=0, timeout=10):
 53 |         """
 54 |         Get the latest updates as json file
 55 | 
 56 |         Args:
 57 |             offset (int): default value 0
 58 |             timeout (int): Timeout for the request post (default value 10)
 59 |         Returns:
 60 |             The json results
 61 |         Raises:
 62 |             TimeoutError
 63 |         """
 64 |         method = 'getUpdates'
 65 |         params = {'timeout': timeout, 'offset': offset}
 66 |         resp = requests.get(self.api_url + method, params)
 67 |         try:
 68 |             return resp.json()['result']
 69 |         except KeyError:
 70 |             print('TimeoutError')
 71 |             exit(1)
 72 | 
 73 |     def __chat_id_response(self) -> int:
 74 |         """
 75 |         Fetches the latest chat id from the client
 76 | 
 77 |         Returns:
 78 |             The latest chat-id of the client
 79 |         Raises:
 80 |             TimeoutError
 81 |         """
 82 |         try:
 83 |             fetch_updates = self.__get_updates()
 84 |             return fetch_updates[0]['message']['chat']['id']
 85 |         except TimeoutError as tm_err:
 86 |             print(tm_err)
 87 |             exit(1)
 88 | 
 89 |     def send_message(self, msg, notification=False) -> object:
 90 |         """
 91 |         Function to send message
 92 | 
 93 |         Args:
 94 |             msg (string): Enter the message to post
 95 |             notification (bool): Disable_Notification (default=False)
 96 |         Returns:
 97 |             The status_code of the post operation (send_message)
 98 |         Raises:
 99 |             IndexError
100 |         """
101 |         try:
102 |             method = 'sendMessage'
103 |             params = {'chat_id': self.__chat_id_response(), 'text': msg,
104 |                       'parse_mode': 'HTML', 'disable_notification': notification}
105 |             return requests.post(self.api_url + method, params)
106 |         except IndexError:
107 |             print('Time out error')
108 |             exit(1)
109 | 
110 |     def send_image(self, img_path) -> object:
111 |         """
112 |         Function to send image via telegram
113 | 
114 |         Args:
115 |             img_path (basestring): Enter the file_path to send the image file
116 |         Returns:
117 |             The status_code of the post operation (send_image)
118 |         Raises:
119 |             FileNotFoundError, InvalidFormatError
120 |         """
121 |         method = 'sendPhoto?' + 'chat_id=' + str(self.__chat_id_response())
122 |         if img_path[-4:] in ['.jpg', '.png']:
123 |             pass
124 |         else:
125 |             print('Invalid File Format, please use .jpg or .png format')
126 |             exit(1)
127 |         try:
128 |             files = {'photo': open(img_path, 'rb')}
129 |             return requests.post(self.api_url + method, files = files)
130 |         except FileNotFoundError as fl_err:
131 |             print(fl_err)
132 |             exit(1)
133 | 
134 |     def send_file(self, file_path) -> object:
135 |         """
136 |         Function to send documents via telegram
137 | 
138 |         Args:
139 |             file_path (basestring): Enter the file_path to send the image file
140 |         Returns:
141 |             The status_code of the post operation (send_document)
142 |         Raises:
143 |             FileNotFoundError, TimeoutError
144 |         """
145 |         method = 'sendDocument?' + 'chat_id=' + str(self.__chat_id_response())
146 |         try:
147 |             files = {'document': open(file_path, 'rb')}
148 |             return requests.post(self.api_url + method, files = files)
149 |         except FileNotFoundError as fn_err:
150 |             print(fn_err)
151 |             exit(1)
152 |         except TimeoutError as tm_err:
153 |             print(tm_err)
154 |             exit(1)
155 | 
156 |     def session_dump(self) -> json:
157 |         """
158 |         Function to Dump all the data from the telegram client during the current session
159 | 
160 |         Returns:
161 |             Dumps the session details in the form of json file.
162 |         Raises:
163 |             IOError
164 |         """
165 |         resp = self.__get_updates()
166 |         try:
167 |             if not os.path.exists(self.dirDownloads):
168 |                 os.mkdir(self.dirDownloads)
169 |             local_path = os.path.join(self.dirDownloads, 'session_dump.json')
170 | 
171 |             with open(local_path, 'w+', encoding='utf-8') as outfile:
172 |                 json.dump(resp, outfile)
173 |         except IOError as io_err:
174 |             print(io_err)
175 |             exit(1)
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |

Classes

186 |
187 |
188 | class BotHandler 189 | (token, dir_download='./downloads') 190 |
191 |
192 |

Initialize the telegram client using tokens to access the HTTP API

193 |

Args

194 |
195 |
token : string
196 |
API token [Mandatory].
197 |
198 |

Args

199 |
200 |
dir_download : string
201 |
-> Storage location for dumping payload [default = "./downloads"]
202 |
203 |
204 | 205 | Expand source code 206 | 207 |
class BotHandler:
208 |     def __init__(self, token, dir_download= './downloads'):
209 |         """
210 |         Initialize the telegram client using tokens to access the HTTP API
211 | 
212 |         Args:
213 |             token (string): API token [Mandatory].
214 |         Args:
215 |             dir_download (string): -> Storage location for dumping payload [default = "./downloads"]
216 |         """
217 |         self.token = token
218 |         self.api_url = "https://api.telegram.org/bot{}/".format(token)
219 |         self.file_url = "https://api.telegram.org/file/bot{}/".format(token)
220 |         self.dirDownloads = dir_download
221 | 
222 |     def __get_updates(self, offset=0, timeout=10):
223 |         """
224 |         Get the latest updates as json file
225 | 
226 |         Args:
227 |             offset (int): default value 0
228 |             timeout (int): Timeout for the request post (default value 10)
229 |         Returns:
230 |             The json results
231 |         Raises:
232 |             TimeoutError
233 |         """
234 |         method = 'getUpdates'
235 |         params = {'timeout': timeout, 'offset': offset}
236 |         resp = requests.get(self.api_url + method, params)
237 |         try:
238 |             return resp.json()['result']
239 |         except KeyError:
240 |             print('TimeoutError')
241 |             exit(1)
242 | 
243 |     def __chat_id_response(self) -> int:
244 |         """
245 |         Fetches the latest chat id from the client
246 | 
247 |         Returns:
248 |             The latest chat-id of the client
249 |         Raises:
250 |             TimeoutError
251 |         """
252 |         try:
253 |             fetch_updates = self.__get_updates()
254 |             return fetch_updates[0]['message']['chat']['id']
255 |         except TimeoutError as tm_err:
256 |             print(tm_err)
257 |             exit(1)
258 | 
259 |     def send_message(self, msg, notification=False) -> object:
260 |         """
261 |         Function to send message
262 | 
263 |         Args:
264 |             msg (string): Enter the message to post
265 |             notification (bool): Disable_Notification (default=False)
266 |         Returns:
267 |             The status_code of the post operation (send_message)
268 |         Raises:
269 |             IndexError
270 |         """
271 |         try:
272 |             method = 'sendMessage'
273 |             params = {'chat_id': self.__chat_id_response(), 'text': msg,
274 |                       'parse_mode': 'HTML', 'disable_notification': notification}
275 |             return requests.post(self.api_url + method, params)
276 |         except IndexError:
277 |             print('Time out error')
278 |             exit(1)
279 | 
280 |     def send_image(self, img_path) -> object:
281 |         """
282 |         Function to send image via telegram
283 | 
284 |         Args:
285 |             img_path (basestring): Enter the file_path to send the image file
286 |         Returns:
287 |             The status_code of the post operation (send_image)
288 |         Raises:
289 |             FileNotFoundError, InvalidFormatError
290 |         """
291 |         method = 'sendPhoto?' + 'chat_id=' + str(self.__chat_id_response())
292 |         if img_path[-4:] in ['.jpg', '.png']:
293 |             pass
294 |         else:
295 |             print('Invalid File Format, please use .jpg or .png format')
296 |             exit(1)
297 |         try:
298 |             files = {'photo': open(img_path, 'rb')}
299 |             return requests.post(self.api_url + method, files = files)
300 |         except FileNotFoundError as fl_err:
301 |             print(fl_err)
302 |             exit(1)
303 | 
304 |     def send_file(self, file_path) -> object:
305 |         """
306 |         Function to send documents via telegram
307 | 
308 |         Args:
309 |             file_path (basestring): Enter the file_path to send the image file
310 |         Returns:
311 |             The status_code of the post operation (send_document)
312 |         Raises:
313 |             FileNotFoundError, TimeoutError
314 |         """
315 |         method = 'sendDocument?' + 'chat_id=' + str(self.__chat_id_response())
316 |         try:
317 |             files = {'document': open(file_path, 'rb')}
318 |             return requests.post(self.api_url + method, files = files)
319 |         except FileNotFoundError as fn_err:
320 |             print(fn_err)
321 |             exit(1)
322 |         except TimeoutError as tm_err:
323 |             print(tm_err)
324 |             exit(1)
325 | 
326 |     def session_dump(self) -> json:
327 |         """
328 |         Function to Dump all the data from the telegram client during the current session
329 | 
330 |         Returns:
331 |             Dumps the session details in the form of json file.
332 |         Raises:
333 |             IOError
334 |         """
335 |         resp = self.__get_updates()
336 |         try:
337 |             if not os.path.exists(self.dirDownloads):
338 |                 os.mkdir(self.dirDownloads)
339 |             local_path = os.path.join(self.dirDownloads, 'session_dump.json')
340 | 
341 |             with open(local_path, 'w+', encoding='utf-8') as outfile:
342 |                 json.dump(resp, outfile)
343 |         except IOError as io_err:
344 |             print(io_err)
345 |             exit(1)
346 |
347 |

Methods

348 |
349 |
350 | def send_file(self, file_path) ‑> object 351 |
352 |
353 |

Function to send documents via telegram

354 |

Args

355 |
356 |
file_path : basestring
357 |
Enter the file_path to send the image file
358 |
359 |

Returns

360 |

The status_code of the post operation (send_document)

361 |

Raises

362 |

FileNotFoundError, TimeoutError

363 |
364 | 365 | Expand source code 366 | 367 |
def send_file(self, file_path) -> object:
368 |     """
369 |     Function to send documents via telegram
370 | 
371 |     Args:
372 |         file_path (basestring): Enter the file_path to send the image file
373 |     Returns:
374 |         The status_code of the post operation (send_document)
375 |     Raises:
376 |         FileNotFoundError, TimeoutError
377 |     """
378 |     method = 'sendDocument?' + 'chat_id=' + str(self.__chat_id_response())
379 |     try:
380 |         files = {'document': open(file_path, 'rb')}
381 |         return requests.post(self.api_url + method, files = files)
382 |     except FileNotFoundError as fn_err:
383 |         print(fn_err)
384 |         exit(1)
385 |     except TimeoutError as tm_err:
386 |         print(tm_err)
387 |         exit(1)
388 |
389 |
390 |
391 | def send_image(self, img_path) ‑> object 392 |
393 |
394 |

Function to send image via telegram

395 |

Args

396 |
397 |
img_path : basestring
398 |
Enter the file_path to send the image file
399 |
400 |

Returns

401 |

The status_code of the post operation (send_image)

402 |

Raises

403 |

FileNotFoundError, InvalidFormatError

404 |
405 | 406 | Expand source code 407 | 408 |
def send_image(self, img_path) -> object:
409 |     """
410 |     Function to send image via telegram
411 | 
412 |     Args:
413 |         img_path (basestring): Enter the file_path to send the image file
414 |     Returns:
415 |         The status_code of the post operation (send_image)
416 |     Raises:
417 |         FileNotFoundError, InvalidFormatError
418 |     """
419 |     method = 'sendPhoto?' + 'chat_id=' + str(self.__chat_id_response())
420 |     if img_path[-4:] in ['.jpg', '.png']:
421 |         pass
422 |     else:
423 |         print('Invalid File Format, please use .jpg or .png format')
424 |         exit(1)
425 |     try:
426 |         files = {'photo': open(img_path, 'rb')}
427 |         return requests.post(self.api_url + method, files = files)
428 |     except FileNotFoundError as fl_err:
429 |         print(fl_err)
430 |         exit(1)
431 |
432 |
433 |
434 | def send_message(self, msg, notification=False) ‑> object 435 |
436 |
437 |

Function to send message

438 |

Args

439 |
440 |
msg : string
441 |
Enter the message to post
442 |
notification : bool
443 |
Disable_Notification (default=False)
444 |
445 |

Returns

446 |

The status_code of the post operation (send_message)

447 |

Raises

448 |

IndexError

449 |
450 | 451 | Expand source code 452 | 453 |
def send_message(self, msg, notification=False) -> object:
454 |     """
455 |     Function to send message
456 | 
457 |     Args:
458 |         msg (string): Enter the message to post
459 |         notification (bool): Disable_Notification (default=False)
460 |     Returns:
461 |         The status_code of the post operation (send_message)
462 |     Raises:
463 |         IndexError
464 |     """
465 |     try:
466 |         method = 'sendMessage'
467 |         params = {'chat_id': self.__chat_id_response(), 'text': msg,
468 |                   'parse_mode': 'HTML', 'disable_notification': notification}
469 |         return requests.post(self.api_url + method, params)
470 |     except IndexError:
471 |         print('Time out error')
472 |         exit(1)
473 |
474 |
475 |
476 | def session_dump(self) ‑>  477 |
478 |
479 |

Function to Dump all the data from the telegram client during the current session

480 |

Returns

481 |

Dumps the session details in the form of json file.

482 |

Raises

483 |

IOError

484 |
485 | 486 | Expand source code 487 | 488 |
def session_dump(self) -> json:
489 |     """
490 |     Function to Dump all the data from the telegram client during the current session
491 | 
492 |     Returns:
493 |         Dumps the session details in the form of json file.
494 |     Raises:
495 |         IOError
496 |     """
497 |     resp = self.__get_updates()
498 |     try:
499 |         if not os.path.exists(self.dirDownloads):
500 |             os.mkdir(self.dirDownloads)
501 |         local_path = os.path.join(self.dirDownloads, 'session_dump.json')
502 | 
503 |         with open(local_path, 'w+', encoding='utf-8') as outfile:
504 |             json.dump(resp, outfile)
505 |     except IOError as io_err:
506 |         print(io_err)
507 |         exit(1)
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 | 541 |
542 | 545 | 546 | -------------------------------------------------------------------------------- /docs/tf_notifier.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | notifly.tf_notifier API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module notifly.tf_notifier

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from notifly import discord, telegram, slack
 30 | import inspect
 31 | import matplotlib.pyplot as plt
 32 | import copy
 33 | 
 34 | 
 35 | class TfNotifier:
 36 | 
 37 |     def __init__(self, token, platform, channel= 'general'):
 38 |         platform = platform.lower()
 39 |         if platform == 'discord':
 40 |             self.notifier = discord.Notifier(token)
 41 |         elif platform == 'telegram':
 42 |             self.notifier = telegram.BotHandler(token)
 43 |         elif platform == 'slack':   #TODO Handle slack channel config
 44 |             self.notifier = slack.Notifier(token, channel)
 45 |         else:
 46 |             print('Invalid Platform')
 47 |             exit(1)
 48 | 
 49 |     @staticmethod
 50 |     def plot_graph(history, current_epoch_logs):
 51 | 
 52 |         # merge history with current epoch logs
 53 |         for key, value in current_epoch_logs.items():
 54 |             if key not in history:
 55 |                 history[key] = [value]
 56 |             else:
 57 |                 history[key].append(value)
 58 | 
 59 |         plt.figure()
 60 |         for key, value in history.items():
 61 |             if len(value) != 1:
 62 |                 plt.plot(range(1, len(value)+1), value, label=str(key))
 63 |             else:
 64 |                 plt.scatter(range(1, len(value)+1), value, label=str(key))
 65 |             plt.title("Training History")
 66 | 
 67 |         plt.xlabel('Epochs')
 68 |         plt.legend()
 69 |         plt.pause(1e-13)
 70 |         file_path = 'fig.png'
 71 |         plt.savefig(file_path, bbox_inches='tight')
 72 |         plt.close()
 73 |         return file_path
 74 | 
 75 |     def notify_on_train_begin(self):
 76 | 
 77 |         def inner(func_to_call):
 78 |             def wrapper(*args, **kwargs):
 79 | 
 80 |                 # get parameter values
 81 |                 parameter_values = []
 82 |                 for i in args:
 83 |                     parameter_values.append(i)
 84 |                 for i in kwargs.values():
 85 |                     parameter_values.append(i)
 86 | 
 87 |                 # get arguments names from the function signature
 88 |                 sig = inspect.signature(func_to_call)
 89 |                 parameter_names = sig.parameters.keys()
 90 | 
 91 |                 # parameter mapping with names to values
 92 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
 93 | 
 94 |                 # get starting logs
 95 |                 starting_logs = parameter_mapping.get('logs')
 96 | 
 97 |                 self.notifier.send_message(f':muscle: training started, got log keys: {starting_logs}')
 98 | 
 99 |                 return_value = func_to_call(*args, **kwargs)
100 | 
101 |                 return return_value
102 | 
103 |             return wrapper
104 | 
105 |         return inner
106 | 
107 |     def notify_on_train_end(self):
108 | 
109 |         def inner(func_to_call):
110 |             def wrapper(*args, **kwargs):
111 | 
112 |                 # get parameter values
113 |                 parameter_values = []
114 |                 for i in args:
115 |                     parameter_values.append(i)
116 |                 for i in kwargs.values():
117 |                     parameter_values.append(i)
118 | 
119 |                 # get arguments names from the function signature
120 |                 sig = inspect.signature(func_to_call)
121 |                 parameter_names = sig.parameters.keys()
122 | 
123 |                 # parameter mapping with names to values
124 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
125 | 
126 |                 # get starting logs
127 |                 ending_logs = parameter_mapping.get('logs')
128 | 
129 |                 self.notifier.send_message(f':tada: training ended, got log keys: {ending_logs}')
130 | 
131 |                 return_value = func_to_call(*args, **kwargs)
132 | 
133 |                 return return_value
134 | 
135 |             return wrapper
136 | 
137 |         return inner
138 | 
139 |     def notify_on_epoch_begin(self, epoch_interval, graph_interval):
140 | 
141 |         def inner(func_to_call):
142 |             def wrapper(*args, **kwargs):
143 | 
144 |                 # get parameter values
145 |                 parameter_values = []
146 |                 for i in args:
147 |                     parameter_values.append(i)
148 |                 for i in kwargs.values():
149 |                     parameter_values.append(i)
150 | 
151 |                 # get arguments names from the function signature
152 |                 sig = inspect.signature(func_to_call)
153 |                 parameter_names = sig.parameters.keys()
154 | 
155 |                 # parameter mapping with names to values
156 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
157 |                 print(parameter_mapping)
158 | 
159 |                 # get model instance
160 |                 model_instance = parameter_mapping.get('self').model
161 | 
162 |                 # get current epoch because epoch starts from 0
163 |                 current_epoch = parameter_mapping.get('epoch') + 1
164 | 
165 |                 # get current epoch logs
166 |                 current_epoch_logs = parameter_mapping.get('logs')
167 |                 print(current_epoch_logs)
168 | 
169 |                 # notify if current_epoch is divisible by epoch_interval
170 |                 if current_epoch % epoch_interval == 0:
171 |                     message = f"epoch: {current_epoch} started, got log keys:"
172 |                     for k, v in current_epoch_logs.items():
173 |                         message += " {}: {:.4f} ".format(k, v)
174 |                     self.notifier.send_message(message)
175 | 
176 |                 # notify graph if current_epoch is divisible by graph_interval
177 |                 if current_epoch % graph_interval == 0:
178 |                     history_copy = copy.deepcopy(model_instance.history.history)
179 |                     file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs)
180 |                     # TODO: change this function call
181 |                     self.notifier.send_file(file_path)
182 | 
183 |                 return_value = func_to_call(*args, **kwargs)
184 | 
185 |                 return return_value
186 | 
187 |             return wrapper
188 | 
189 |         return inner
190 | 
191 |     def notify_on_epoch_end(self, epoch_interval, graph_interval):
192 | 
193 |         def inner(func_to_call):
194 |             def wrapper(*args, **kwargs):
195 | 
196 |                 # get parameter values
197 |                 parameter_values = []
198 |                 for i in args:
199 |                     parameter_values.append(i)
200 |                 for i in kwargs.values():
201 |                     parameter_values.append(i)
202 | 
203 |                 # get arguments names from the function signature
204 |                 sig = inspect.signature(func_to_call)
205 |                 parameter_names = sig.parameters.keys()
206 | 
207 |                 # parameter mapping with names to values
208 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
209 |                 print(parameter_mapping)
210 | 
211 |                 # get model instance
212 |                 model_instance = parameter_mapping.get('self').model
213 | 
214 |                 # get current epoch because epoch starts from 0
215 |                 current_epoch = parameter_mapping.get('epoch') + 1
216 | 
217 |                 # get current epoch logs
218 |                 current_epoch_logs = parameter_mapping.get('logs')
219 | 
220 |                 # notify if current_epoch is divisible by epoch_interval
221 |                 if current_epoch % epoch_interval == 0:
222 |                     message = f"epoch: {current_epoch} ended, got log keys:"
223 |                     for k, v in current_epoch_logs.items():
224 |                         message += " {}: {:.4f} ".format(k, v)
225 |                     self.notifier.send_message(message)
226 | 
227 |                 # notify graph if current_epoch is divisible by graph_interval
228 |                 if current_epoch % graph_interval == 0:
229 |                     history_copy = copy.deepcopy(model_instance.history.history)
230 |                     file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs)
231 |                     # TODO: change this function call
232 |                     self.notifier.send_file(file_path)
233 | 
234 |                 return_value = func_to_call(*args, **kwargs)
235 | 
236 |                 return return_value
237 | 
238 |             return wrapper
239 | 
240 |         return inner
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |

Classes

251 |
252 |
253 | class TfNotifier 254 | (token, platform, channel='general') 255 |
256 |
257 |
258 |
259 | 260 | Expand source code 261 | 262 |
class TfNotifier:
263 | 
264 |     def __init__(self, token, platform, channel= 'general'):
265 |         platform = platform.lower()
266 |         if platform == 'discord':
267 |             self.notifier = discord.Notifier(token)
268 |         elif platform == 'telegram':
269 |             self.notifier = telegram.BotHandler(token)
270 |         elif platform == 'slack':   #TODO Handle slack channel config
271 |             self.notifier = slack.Notifier(token, channel)
272 |         else:
273 |             print('Invalid Platform')
274 |             exit(1)
275 | 
276 |     @staticmethod
277 |     def plot_graph(history, current_epoch_logs):
278 | 
279 |         # merge history with current epoch logs
280 |         for key, value in current_epoch_logs.items():
281 |             if key not in history:
282 |                 history[key] = [value]
283 |             else:
284 |                 history[key].append(value)
285 | 
286 |         plt.figure()
287 |         for key, value in history.items():
288 |             if len(value) != 1:
289 |                 plt.plot(range(1, len(value)+1), value, label=str(key))
290 |             else:
291 |                 plt.scatter(range(1, len(value)+1), value, label=str(key))
292 |             plt.title("Training History")
293 | 
294 |         plt.xlabel('Epochs')
295 |         plt.legend()
296 |         plt.pause(1e-13)
297 |         file_path = 'fig.png'
298 |         plt.savefig(file_path, bbox_inches='tight')
299 |         plt.close()
300 |         return file_path
301 | 
302 |     def notify_on_train_begin(self):
303 | 
304 |         def inner(func_to_call):
305 |             def wrapper(*args, **kwargs):
306 | 
307 |                 # get parameter values
308 |                 parameter_values = []
309 |                 for i in args:
310 |                     parameter_values.append(i)
311 |                 for i in kwargs.values():
312 |                     parameter_values.append(i)
313 | 
314 |                 # get arguments names from the function signature
315 |                 sig = inspect.signature(func_to_call)
316 |                 parameter_names = sig.parameters.keys()
317 | 
318 |                 # parameter mapping with names to values
319 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
320 | 
321 |                 # get starting logs
322 |                 starting_logs = parameter_mapping.get('logs')
323 | 
324 |                 self.notifier.send_message(f':muscle: training started, got log keys: {starting_logs}')
325 | 
326 |                 return_value = func_to_call(*args, **kwargs)
327 | 
328 |                 return return_value
329 | 
330 |             return wrapper
331 | 
332 |         return inner
333 | 
334 |     def notify_on_train_end(self):
335 | 
336 |         def inner(func_to_call):
337 |             def wrapper(*args, **kwargs):
338 | 
339 |                 # get parameter values
340 |                 parameter_values = []
341 |                 for i in args:
342 |                     parameter_values.append(i)
343 |                 for i in kwargs.values():
344 |                     parameter_values.append(i)
345 | 
346 |                 # get arguments names from the function signature
347 |                 sig = inspect.signature(func_to_call)
348 |                 parameter_names = sig.parameters.keys()
349 | 
350 |                 # parameter mapping with names to values
351 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
352 | 
353 |                 # get starting logs
354 |                 ending_logs = parameter_mapping.get('logs')
355 | 
356 |                 self.notifier.send_message(f':tada: training ended, got log keys: {ending_logs}')
357 | 
358 |                 return_value = func_to_call(*args, **kwargs)
359 | 
360 |                 return return_value
361 | 
362 |             return wrapper
363 | 
364 |         return inner
365 | 
366 |     def notify_on_epoch_begin(self, epoch_interval, graph_interval):
367 | 
368 |         def inner(func_to_call):
369 |             def wrapper(*args, **kwargs):
370 | 
371 |                 # get parameter values
372 |                 parameter_values = []
373 |                 for i in args:
374 |                     parameter_values.append(i)
375 |                 for i in kwargs.values():
376 |                     parameter_values.append(i)
377 | 
378 |                 # get arguments names from the function signature
379 |                 sig = inspect.signature(func_to_call)
380 |                 parameter_names = sig.parameters.keys()
381 | 
382 |                 # parameter mapping with names to values
383 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
384 |                 print(parameter_mapping)
385 | 
386 |                 # get model instance
387 |                 model_instance = parameter_mapping.get('self').model
388 | 
389 |                 # get current epoch because epoch starts from 0
390 |                 current_epoch = parameter_mapping.get('epoch') + 1
391 | 
392 |                 # get current epoch logs
393 |                 current_epoch_logs = parameter_mapping.get('logs')
394 |                 print(current_epoch_logs)
395 | 
396 |                 # notify if current_epoch is divisible by epoch_interval
397 |                 if current_epoch % epoch_interval == 0:
398 |                     message = f"epoch: {current_epoch} started, got log keys:"
399 |                     for k, v in current_epoch_logs.items():
400 |                         message += " {}: {:.4f} ".format(k, v)
401 |                     self.notifier.send_message(message)
402 | 
403 |                 # notify graph if current_epoch is divisible by graph_interval
404 |                 if current_epoch % graph_interval == 0:
405 |                     history_copy = copy.deepcopy(model_instance.history.history)
406 |                     file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs)
407 |                     # TODO: change this function call
408 |                     self.notifier.send_file(file_path)
409 | 
410 |                 return_value = func_to_call(*args, **kwargs)
411 | 
412 |                 return return_value
413 | 
414 |             return wrapper
415 | 
416 |         return inner
417 | 
418 |     def notify_on_epoch_end(self, epoch_interval, graph_interval):
419 | 
420 |         def inner(func_to_call):
421 |             def wrapper(*args, **kwargs):
422 | 
423 |                 # get parameter values
424 |                 parameter_values = []
425 |                 for i in args:
426 |                     parameter_values.append(i)
427 |                 for i in kwargs.values():
428 |                     parameter_values.append(i)
429 | 
430 |                 # get arguments names from the function signature
431 |                 sig = inspect.signature(func_to_call)
432 |                 parameter_names = sig.parameters.keys()
433 | 
434 |                 # parameter mapping with names to values
435 |                 parameter_mapping = dict(zip(parameter_names, parameter_values))
436 |                 print(parameter_mapping)
437 | 
438 |                 # get model instance
439 |                 model_instance = parameter_mapping.get('self').model
440 | 
441 |                 # get current epoch because epoch starts from 0
442 |                 current_epoch = parameter_mapping.get('epoch') + 1
443 | 
444 |                 # get current epoch logs
445 |                 current_epoch_logs = parameter_mapping.get('logs')
446 | 
447 |                 # notify if current_epoch is divisible by epoch_interval
448 |                 if current_epoch % epoch_interval == 0:
449 |                     message = f"epoch: {current_epoch} ended, got log keys:"
450 |                     for k, v in current_epoch_logs.items():
451 |                         message += " {}: {:.4f} ".format(k, v)
452 |                     self.notifier.send_message(message)
453 | 
454 |                 # notify graph if current_epoch is divisible by graph_interval
455 |                 if current_epoch % graph_interval == 0:
456 |                     history_copy = copy.deepcopy(model_instance.history.history)
457 |                     file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs)
458 |                     # TODO: change this function call
459 |                     self.notifier.send_file(file_path)
460 | 
461 |                 return_value = func_to_call(*args, **kwargs)
462 | 
463 |                 return return_value
464 | 
465 |             return wrapper
466 | 
467 |         return inner
468 |
469 |

Static methods

470 |
471 |
472 | def plot_graph(history, current_epoch_logs) 473 |
474 |
475 |
476 |
477 | 478 | Expand source code 479 | 480 |
@staticmethod
481 | def plot_graph(history, current_epoch_logs):
482 | 
483 |     # merge history with current epoch logs
484 |     for key, value in current_epoch_logs.items():
485 |         if key not in history:
486 |             history[key] = [value]
487 |         else:
488 |             history[key].append(value)
489 | 
490 |     plt.figure()
491 |     for key, value in history.items():
492 |         if len(value) != 1:
493 |             plt.plot(range(1, len(value)+1), value, label=str(key))
494 |         else:
495 |             plt.scatter(range(1, len(value)+1), value, label=str(key))
496 |         plt.title("Training History")
497 | 
498 |     plt.xlabel('Epochs')
499 |     plt.legend()
500 |     plt.pause(1e-13)
501 |     file_path = 'fig.png'
502 |     plt.savefig(file_path, bbox_inches='tight')
503 |     plt.close()
504 |     return file_path
505 |
506 |
507 |
508 |

Methods

509 |
510 |
511 | def notify_on_epoch_begin(self, epoch_interval, graph_interval) 512 |
513 |
514 |
515 |
516 | 517 | Expand source code 518 | 519 |
def notify_on_epoch_begin(self, epoch_interval, graph_interval):
520 | 
521 |     def inner(func_to_call):
522 |         def wrapper(*args, **kwargs):
523 | 
524 |             # get parameter values
525 |             parameter_values = []
526 |             for i in args:
527 |                 parameter_values.append(i)
528 |             for i in kwargs.values():
529 |                 parameter_values.append(i)
530 | 
531 |             # get arguments names from the function signature
532 |             sig = inspect.signature(func_to_call)
533 |             parameter_names = sig.parameters.keys()
534 | 
535 |             # parameter mapping with names to values
536 |             parameter_mapping = dict(zip(parameter_names, parameter_values))
537 |             print(parameter_mapping)
538 | 
539 |             # get model instance
540 |             model_instance = parameter_mapping.get('self').model
541 | 
542 |             # get current epoch because epoch starts from 0
543 |             current_epoch = parameter_mapping.get('epoch') + 1
544 | 
545 |             # get current epoch logs
546 |             current_epoch_logs = parameter_mapping.get('logs')
547 |             print(current_epoch_logs)
548 | 
549 |             # notify if current_epoch is divisible by epoch_interval
550 |             if current_epoch % epoch_interval == 0:
551 |                 message = f"epoch: {current_epoch} started, got log keys:"
552 |                 for k, v in current_epoch_logs.items():
553 |                     message += " {}: {:.4f} ".format(k, v)
554 |                 self.notifier.send_message(message)
555 | 
556 |             # notify graph if current_epoch is divisible by graph_interval
557 |             if current_epoch % graph_interval == 0:
558 |                 history_copy = copy.deepcopy(model_instance.history.history)
559 |                 file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs)
560 |                 # TODO: change this function call
561 |                 self.notifier.send_file(file_path)
562 | 
563 |             return_value = func_to_call(*args, **kwargs)
564 | 
565 |             return return_value
566 | 
567 |         return wrapper
568 | 
569 |     return inner
570 |
571 |
572 |
573 | def notify_on_epoch_end(self, epoch_interval, graph_interval) 574 |
575 |
576 |
577 |
578 | 579 | Expand source code 580 | 581 |
def notify_on_epoch_end(self, epoch_interval, graph_interval):
582 | 
583 |     def inner(func_to_call):
584 |         def wrapper(*args, **kwargs):
585 | 
586 |             # get parameter values
587 |             parameter_values = []
588 |             for i in args:
589 |                 parameter_values.append(i)
590 |             for i in kwargs.values():
591 |                 parameter_values.append(i)
592 | 
593 |             # get arguments names from the function signature
594 |             sig = inspect.signature(func_to_call)
595 |             parameter_names = sig.parameters.keys()
596 | 
597 |             # parameter mapping with names to values
598 |             parameter_mapping = dict(zip(parameter_names, parameter_values))
599 |             print(parameter_mapping)
600 | 
601 |             # get model instance
602 |             model_instance = parameter_mapping.get('self').model
603 | 
604 |             # get current epoch because epoch starts from 0
605 |             current_epoch = parameter_mapping.get('epoch') + 1
606 | 
607 |             # get current epoch logs
608 |             current_epoch_logs = parameter_mapping.get('logs')
609 | 
610 |             # notify if current_epoch is divisible by epoch_interval
611 |             if current_epoch % epoch_interval == 0:
612 |                 message = f"epoch: {current_epoch} ended, got log keys:"
613 |                 for k, v in current_epoch_logs.items():
614 |                     message += " {}: {:.4f} ".format(k, v)
615 |                 self.notifier.send_message(message)
616 | 
617 |             # notify graph if current_epoch is divisible by graph_interval
618 |             if current_epoch % graph_interval == 0:
619 |                 history_copy = copy.deepcopy(model_instance.history.history)
620 |                 file_path = TfNotifier.plot_graph(history_copy, current_epoch_logs)
621 |                 # TODO: change this function call
622 |                 self.notifier.send_file(file_path)
623 | 
624 |             return_value = func_to_call(*args, **kwargs)
625 | 
626 |             return return_value
627 | 
628 |         return wrapper
629 | 
630 |     return inner
631 |
632 |
633 |
634 | def notify_on_train_begin(self) 635 |
636 |
637 |
638 |
639 | 640 | Expand source code 641 | 642 |
def notify_on_train_begin(self):
643 | 
644 |     def inner(func_to_call):
645 |         def wrapper(*args, **kwargs):
646 | 
647 |             # get parameter values
648 |             parameter_values = []
649 |             for i in args:
650 |                 parameter_values.append(i)
651 |             for i in kwargs.values():
652 |                 parameter_values.append(i)
653 | 
654 |             # get arguments names from the function signature
655 |             sig = inspect.signature(func_to_call)
656 |             parameter_names = sig.parameters.keys()
657 | 
658 |             # parameter mapping with names to values
659 |             parameter_mapping = dict(zip(parameter_names, parameter_values))
660 | 
661 |             # get starting logs
662 |             starting_logs = parameter_mapping.get('logs')
663 | 
664 |             self.notifier.send_message(f':muscle: training started, got log keys: {starting_logs}')
665 | 
666 |             return_value = func_to_call(*args, **kwargs)
667 | 
668 |             return return_value
669 | 
670 |         return wrapper
671 | 
672 |     return inner
673 |
674 |
675 |
676 | def notify_on_train_end(self) 677 |
678 |
679 |
680 |
681 | 682 | Expand source code 683 | 684 |
def notify_on_train_end(self):
685 | 
686 |     def inner(func_to_call):
687 |         def wrapper(*args, **kwargs):
688 | 
689 |             # get parameter values
690 |             parameter_values = []
691 |             for i in args:
692 |                 parameter_values.append(i)
693 |             for i in kwargs.values():
694 |                 parameter_values.append(i)
695 | 
696 |             # get arguments names from the function signature
697 |             sig = inspect.signature(func_to_call)
698 |             parameter_names = sig.parameters.keys()
699 | 
700 |             # parameter mapping with names to values
701 |             parameter_mapping = dict(zip(parameter_names, parameter_values))
702 | 
703 |             # get starting logs
704 |             ending_logs = parameter_mapping.get('logs')
705 | 
706 |             self.notifier.send_message(f':tada: training ended, got log keys: {ending_logs}')
707 | 
708 |             return_value = func_to_call(*args, **kwargs)
709 | 
710 |             return return_value
711 | 
712 |         return wrapper
713 | 
714 |     return inner
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 | 749 |
750 | 753 | 754 | --------------------------------------------------------------------------------