Skip to content

Commit

Permalink
Add new optional parameter on the job() wrapper to sync monitor attr… (
Browse files Browse the repository at this point in the history
…#35)

* Add new optional parameter on the job() wrapper to  sync monitor attributes at startup

* Update readme

* Add a test that will always run first to test the auto-sync that happens on module import
  • Loading branch information
shaneharter authored May 23, 2024
1 parent bf3114b commit 72c98b0
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 3 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ def send_invoices_task(*args, **kwargs):
...
```

#### You can provide monitor attributes that will be synced when your app starts

To sync attributes, provide an API key with monitor:write privileges.

```python
import cronitor

# Copy your SDK Integration key from https://cronitor.io/settings/api
cronitor.api_key = 'apiKey123'

@cronitor.job('send-invoices', attributes={'schedule': '0 8 * * *', 'notify': ['devops-alerts']})
def send_invoices_task(*args, **kwargs):
...
```

## Sending Telemetry Events

If you want to send a heartbeat events, or want finer control over when/how [telemetry events](https://cronitor.io/docs/telemetry-api) are sent for your jobs, you can create a monitor instance and call the `.ping` method.
Expand Down
30 changes: 28 additions & 2 deletions cronitor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from datetime import datetime
from functools import wraps
import sys
import requests
import yaml
from yaml.loader import SafeLoader
import time
import atexit
import threading

from .monitor import Monitor, YAML

Expand All @@ -23,6 +25,9 @@

celerybeat_only = False

# monitor attributes can be synced at process startup
monitor_attributes = []

# this is a pointer to the module object instance itself.
this = sys.modules[__name__]
if this.config:
Expand Down Expand Up @@ -50,7 +55,12 @@ class State(object):
FAIL = 'fail'

# include_output is deprecated in favor of log_output and can be removed in 5.0 release
def job(key, env=None, log_output=True, include_output=True):
def job(key, env=None, log_output=True, include_output=True, attributes=None):

if type(attributes) is dict:
attributes['key'] = key
monitor_attributes.append(attributes)

def wrapper(func):
@wraps(func)
def wrapped(*args, **kwargs):
Expand Down Expand Up @@ -108,3 +118,19 @@ def read_config(path=None, output=False):
data = yaml.load(conf, Loader=SafeLoader)
if output:
return data

def sync_monitors(wait=1):
global monitor_attributes
if wait > 0:
time.sleep(wait)

if len(monitor_attributes):
Monitor.put(monitor_attributes)
monitor_attributes = []

try:
sync
except NameError:
sync = threading.Thread(target=sync_monitors)
sync.start()
atexit.register(sync.join)
29 changes: 29 additions & 0 deletions cronitor/tests/test_00.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import yaml
import cronitor
import unittest
from unittest.mock import call, patch, ANY
import time
import cronitor

FAKE_API_KEY = 'cb54ac4fd16142469f2d84fc1bbebd84XXXDEADXXX'
YAML_PATH = './cronitor/tests/cronitor.yaml'

cronitor.api_key = FAKE_API_KEY
cronitor.timeout = 10

class SyncTests(unittest.TestCase):

def setUp(self):
return super().setUp()

def test_00_monitor_attributes_are_put(self):
# This test will run first, test that attributes are synced correctly, and then undo the global mock

with patch('cronitor.Monitor.put') as mock_put:
time.sleep(2)
calls = [call([{'key': 'ping-decorator-test', 'name': 'Ping Decorator Test'}])]
mock_put.assert_has_calls(calls)

@cronitor.job('ping-decorator-test', attributes={'name': 'Ping Decorator Test'})
def function_call_with_attributes(self):
return
4 changes: 3 additions & 1 deletion cronitor/tests/test_pings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import unittest
from unittest.mock import patch, ANY, call
from unittest.mock import MagicMock

import cronitor
import pytest

# a reserved monitorkey for running integration tests against cronitor.link
FAKE_KEY = 'd3x0c1'
Expand Down Expand Up @@ -83,6 +83,7 @@ def test_ping_wraps_function_raises_exception(self, mocked_ping):
self.assertRaises(Exception, lambda: self.error_function_call())
mocked_ping.assert_has_calls(calls)


@patch('cronitor.Monitor.ping')
@patch('cronitor.Monitor.__init__')
def test_ping_with_non_default_env(self, mocked_monitor, mocked_ping):
Expand All @@ -104,3 +105,4 @@ def staging_env_function_call(self):




0 comments on commit 72c98b0

Please sign in to comment.