watchup 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
watchup-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Watchup Ltd
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.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include watchup *.py
watchup-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.4
2
+ Name: watchup
3
+ Version: 1.0.0
4
+ Summary: Watchup SDK for Python - Error and event tracking
5
+ Home-page: https://github.com/watchupltd/watchup-python
6
+ Author: Watchup Ltd
7
+ Author-email: support@watchup.com
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: System :: Monitoring
20
+ Requires-Python: >=3.7
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
25
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
26
+ Requires-Dist: black>=22.0.0; extra == "dev"
27
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: license-file
35
+ Dynamic: provides-extra
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ # Watchup Python SDK
40
+
41
+ Official Python SDK for Watchup incident tracking.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install watchup
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ import watchup
53
+
54
+ # Initialize the client
55
+ watchup.init(
56
+ api_key="your-api-key",
57
+ project_id="your-project-id",
58
+ environment="production"
59
+ )
60
+
61
+ # Capture an error
62
+ try:
63
+ risky_operation()
64
+ except Exception as e:
65
+ watchup.capture_error(e)
66
+
67
+ # Capture a message
68
+ watchup.capture_message("User completed checkout", level="info")
69
+
70
+ # Set user context
71
+ watchup.set_user({
72
+ "id": "user-123",
73
+ "email": "user@example.com",
74
+ "username": "john_doe"
75
+ })
76
+
77
+ # Add breadcrumbs
78
+ watchup.add_breadcrumb({
79
+ "message": "User clicked checkout button",
80
+ "category": "user-action",
81
+ "level": "info"
82
+ })
83
+
84
+ # Set tags
85
+ watchup.set_tag("version", "1.2.3")
86
+ watchup.set_tags({
87
+ "browser": "chrome",
88
+ "os": "windows"
89
+ })
90
+
91
+ # Flush events before exit
92
+ watchup.flush()
93
+ ```
94
+
95
+ ## Configuration
96
+
97
+ ```python
98
+ watchup.init(
99
+ api_key="your-api-key", # Required
100
+ project_id="your-project-id", # Required
101
+ endpoint="https://api.watchup.com/api/v1/events", # Optional
102
+ environment="production", # Optional
103
+ release="1.2.3", # Optional
104
+ server_name="web-server-01", # Optional
105
+ batch_interval=5000, # Optional (ms)
106
+ max_batch_size=10, # Optional
107
+ max_queue_size=100, # Optional
108
+ max_retries=3, # Optional
109
+ retry_backoff=1000, # Optional (ms)
110
+ enabled=True, # Optional
111
+ debug=False, # Optional
112
+ sample_rate=1.0, # Optional (0.0-1.0)
113
+ before_send=lambda event: event, # Optional
114
+ before_breadcrumb=lambda bc: bc # Optional
115
+ )
116
+ ```
117
+
118
+ ## Decorator Usage
119
+
120
+ ```python
121
+ @watchup.capture_errors
122
+ def risky_function():
123
+ # Errors will be automatically captured
124
+ raise ValueError("Something went wrong")
125
+ ```
126
+
127
+ ## Context Manager
128
+
129
+ ```python
130
+ with watchup.configure_scope() as scope:
131
+ scope.set_user({"id": "123"})
132
+ scope.set_tag("version", "1.0")
133
+ scope.capture_message("Custom message")
134
+ ```
135
+
136
+ ## Framework Integration
137
+
138
+ ### Flask
139
+
140
+ ```python
141
+ from flask import Flask
142
+ import watchup
143
+
144
+ app = Flask(__name__)
145
+
146
+ watchup.init(
147
+ api_key="your-api-key",
148
+ project_id="your-project-id"
149
+ )
150
+
151
+ @app.errorhandler(Exception)
152
+ def handle_exception(e):
153
+ watchup.capture_error(e)
154
+ return "Internal Server Error", 500
155
+ ```
156
+
157
+ ### Django
158
+
159
+ ```python
160
+ # settings.py
161
+ import watchup
162
+
163
+ watchup.init(
164
+ api_key="your-api-key",
165
+ project_id="your-project-id"
166
+ )
167
+
168
+ # middleware.py
169
+ class WatchupMiddleware:
170
+ def __init__(self, get_response):
171
+ self.get_response = get_response
172
+
173
+ def __call__(self, request):
174
+ return self.get_response(request)
175
+
176
+ def process_exception(self, request, exception):
177
+ watchup.capture_error(exception)
178
+ return None
179
+
180
+ # settings.py
181
+ MIDDLEWARE = [
182
+ 'yourapp.middleware.WatchupMiddleware',
183
+ # ... other middleware
184
+ ]
185
+ ```
186
+
187
+ ## API Reference
188
+
189
+ ### `init(api_key, project_id, **options)`
190
+ Initialize the Watchup client.
191
+
192
+ ### `capture_error(error, context=None)`
193
+ Capture an exception. Returns event ID or None.
194
+
195
+ ### `capture_message(message, level="info", context=None)`
196
+ Capture a message. Returns event ID or None.
197
+
198
+ ### `set_user(user)`
199
+ Set user context. Pass `None` to clear.
200
+
201
+ ### `add_breadcrumb(breadcrumb)`
202
+ Add a breadcrumb to the timeline.
203
+
204
+ ### `set_tag(key, value)`
205
+ Set a single tag.
206
+
207
+ ### `set_tags(tags)`
208
+ Set multiple tags.
209
+
210
+ ### `set_extra(key, value)`
211
+ Set extra context data.
212
+
213
+ ### `set_extras(extras)`
214
+ Set multiple extra context data.
215
+
216
+ ### `flush(timeout=5.0)`
217
+ Flush all queued events. Returns True if successful.
218
+
219
+ ### `close(timeout=5.0)`
220
+ Close the client and cleanup resources.
221
+
222
+ ## License
223
+
224
+ MIT License
@@ -0,0 +1,186 @@
1
+ # Watchup Python SDK
2
+
3
+ Official Python SDK for Watchup incident tracking.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install watchup
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import watchup
15
+
16
+ # Initialize the client
17
+ watchup.init(
18
+ api_key="your-api-key",
19
+ project_id="your-project-id",
20
+ environment="production"
21
+ )
22
+
23
+ # Capture an error
24
+ try:
25
+ risky_operation()
26
+ except Exception as e:
27
+ watchup.capture_error(e)
28
+
29
+ # Capture a message
30
+ watchup.capture_message("User completed checkout", level="info")
31
+
32
+ # Set user context
33
+ watchup.set_user({
34
+ "id": "user-123",
35
+ "email": "user@example.com",
36
+ "username": "john_doe"
37
+ })
38
+
39
+ # Add breadcrumbs
40
+ watchup.add_breadcrumb({
41
+ "message": "User clicked checkout button",
42
+ "category": "user-action",
43
+ "level": "info"
44
+ })
45
+
46
+ # Set tags
47
+ watchup.set_tag("version", "1.2.3")
48
+ watchup.set_tags({
49
+ "browser": "chrome",
50
+ "os": "windows"
51
+ })
52
+
53
+ # Flush events before exit
54
+ watchup.flush()
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ ```python
60
+ watchup.init(
61
+ api_key="your-api-key", # Required
62
+ project_id="your-project-id", # Required
63
+ endpoint="https://api.watchup.com/api/v1/events", # Optional
64
+ environment="production", # Optional
65
+ release="1.2.3", # Optional
66
+ server_name="web-server-01", # Optional
67
+ batch_interval=5000, # Optional (ms)
68
+ max_batch_size=10, # Optional
69
+ max_queue_size=100, # Optional
70
+ max_retries=3, # Optional
71
+ retry_backoff=1000, # Optional (ms)
72
+ enabled=True, # Optional
73
+ debug=False, # Optional
74
+ sample_rate=1.0, # Optional (0.0-1.0)
75
+ before_send=lambda event: event, # Optional
76
+ before_breadcrumb=lambda bc: bc # Optional
77
+ )
78
+ ```
79
+
80
+ ## Decorator Usage
81
+
82
+ ```python
83
+ @watchup.capture_errors
84
+ def risky_function():
85
+ # Errors will be automatically captured
86
+ raise ValueError("Something went wrong")
87
+ ```
88
+
89
+ ## Context Manager
90
+
91
+ ```python
92
+ with watchup.configure_scope() as scope:
93
+ scope.set_user({"id": "123"})
94
+ scope.set_tag("version", "1.0")
95
+ scope.capture_message("Custom message")
96
+ ```
97
+
98
+ ## Framework Integration
99
+
100
+ ### Flask
101
+
102
+ ```python
103
+ from flask import Flask
104
+ import watchup
105
+
106
+ app = Flask(__name__)
107
+
108
+ watchup.init(
109
+ api_key="your-api-key",
110
+ project_id="your-project-id"
111
+ )
112
+
113
+ @app.errorhandler(Exception)
114
+ def handle_exception(e):
115
+ watchup.capture_error(e)
116
+ return "Internal Server Error", 500
117
+ ```
118
+
119
+ ### Django
120
+
121
+ ```python
122
+ # settings.py
123
+ import watchup
124
+
125
+ watchup.init(
126
+ api_key="your-api-key",
127
+ project_id="your-project-id"
128
+ )
129
+
130
+ # middleware.py
131
+ class WatchupMiddleware:
132
+ def __init__(self, get_response):
133
+ self.get_response = get_response
134
+
135
+ def __call__(self, request):
136
+ return self.get_response(request)
137
+
138
+ def process_exception(self, request, exception):
139
+ watchup.capture_error(exception)
140
+ return None
141
+
142
+ # settings.py
143
+ MIDDLEWARE = [
144
+ 'yourapp.middleware.WatchupMiddleware',
145
+ # ... other middleware
146
+ ]
147
+ ```
148
+
149
+ ## API Reference
150
+
151
+ ### `init(api_key, project_id, **options)`
152
+ Initialize the Watchup client.
153
+
154
+ ### `capture_error(error, context=None)`
155
+ Capture an exception. Returns event ID or None.
156
+
157
+ ### `capture_message(message, level="info", context=None)`
158
+ Capture a message. Returns event ID or None.
159
+
160
+ ### `set_user(user)`
161
+ Set user context. Pass `None` to clear.
162
+
163
+ ### `add_breadcrumb(breadcrumb)`
164
+ Add a breadcrumb to the timeline.
165
+
166
+ ### `set_tag(key, value)`
167
+ Set a single tag.
168
+
169
+ ### `set_tags(tags)`
170
+ Set multiple tags.
171
+
172
+ ### `set_extra(key, value)`
173
+ Set extra context data.
174
+
175
+ ### `set_extras(extras)`
176
+ Set multiple extra context data.
177
+
178
+ ### `flush(timeout=5.0)`
179
+ Flush all queued events. Returns True if successful.
180
+
181
+ ### `close(timeout=5.0)`
182
+ Close the client and cleanup resources.
183
+
184
+ ## License
185
+
186
+ MIT License
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
watchup-1.0.0/setup.py ADDED
@@ -0,0 +1,42 @@
1
+ """Setup configuration for Watchup Python SDK."""
2
+
3
+ from setuptools import setup, find_packages
4
+
5
+ with open("README.md", "r", encoding="utf-8") as fh:
6
+ long_description = fh.read()
7
+
8
+ setup(
9
+ name="watchup",
10
+ version="1.0.0",
11
+ author="Watchup Ltd",
12
+ author_email="support@watchup.com",
13
+ description="Watchup SDK for Python - Error and event tracking",
14
+ long_description=long_description,
15
+ long_description_content_type="text/markdown",
16
+ url="https://github.com/watchupltd/watchup-python",
17
+ packages=find_packages(exclude=["tests", "tests.*"]),
18
+ classifiers=[
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.7",
25
+ "Programming Language :: Python :: 3.8",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ "Topic :: System :: Monitoring",
31
+ ],
32
+ python_requires=">=3.7",
33
+ install_requires=[],
34
+ extras_require={
35
+ "dev": [
36
+ "pytest>=7.0.0",
37
+ "pytest-cov>=3.0.0",
38
+ "black>=22.0.0",
39
+ "flake8>=4.0.0",
40
+ ],
41
+ },
42
+ )
@@ -0,0 +1,65 @@
1
+ """Tests for client functionality."""
2
+
3
+ import unittest
4
+ import time
5
+ from watchup import Client
6
+
7
+
8
+ class TestClient(unittest.TestCase):
9
+ """Test client functionality."""
10
+
11
+ def setUp(self):
12
+ """Set up test client."""
13
+ self.client = Client({
14
+ 'api_key': 'test-key',
15
+ 'project_id': 'test-project',
16
+ 'enabled': False, # Disable to avoid actual API calls
17
+ 'debug': True
18
+ })
19
+
20
+ def test_client_initialization(self):
21
+ """Test client initializes correctly."""
22
+ self.assertEqual(self.client.api_key, 'test-key')
23
+ self.assertEqual(self.client.project_id, 'test-project')
24
+ self.assertEqual(self.client.environment, 'production')
25
+
26
+ def test_set_user(self):
27
+ """Test setting user context."""
28
+ self.client.set_user({
29
+ 'id': 'user-123',
30
+ 'email': 'test@example.com'
31
+ })
32
+ self.assertIsNotNone(self.client._user)
33
+ self.assertEqual(self.client._user.id, 'user-123')
34
+
35
+ def test_set_tags(self):
36
+ """Test setting tags."""
37
+ self.client.set_tag('version', '1.0.0')
38
+ self.assertEqual(self.client._tags['version'], '1.0.0')
39
+
40
+ self.client.set_tags({'env': 'test', 'region': 'us-east'})
41
+ self.assertEqual(self.client._tags['env'], 'test')
42
+ self.assertEqual(self.client._tags['region'], 'us-east')
43
+
44
+ def test_add_breadcrumb(self):
45
+ """Test adding breadcrumbs."""
46
+ self.client.add_breadcrumb({
47
+ 'message': 'User clicked button',
48
+ 'category': 'user-action',
49
+ 'level': 'info'
50
+ })
51
+ self.assertEqual(len(self.client._breadcrumbs), 1)
52
+ self.assertEqual(self.client._breadcrumbs[0].message, 'User clicked button')
53
+
54
+ def test_breadcrumb_limit(self):
55
+ """Test breadcrumb limit."""
56
+ for i in range(150):
57
+ self.client.add_breadcrumb({
58
+ 'message': f'Breadcrumb {i}',
59
+ 'category': 'test'
60
+ })
61
+ self.assertLessEqual(len(self.client._breadcrumbs), 100)
62
+
63
+
64
+ if __name__ == '__main__':
65
+ unittest.main()
@@ -0,0 +1,55 @@
1
+ """Tests for fingerprinting."""
2
+
3
+ import unittest
4
+ from watchup.fingerprint import normalize_message, normalize_stack_trace, generate_fingerprint
5
+
6
+
7
+ class TestFingerprinting(unittest.TestCase):
8
+ """Test fingerprinting functionality."""
9
+
10
+ def test_normalize_message_numbers(self):
11
+ """Test that numbers are normalized."""
12
+ result = normalize_message("User 123 not found")
13
+ self.assertEqual(result, "User N not found")
14
+
15
+ def test_normalize_message_hex(self):
16
+ """Test that hex values are normalized."""
17
+ result = normalize_message("Memory address 0x7f8a9b")
18
+ self.assertEqual(result, "Memory address HEX")
19
+
20
+ def test_normalize_message_uuid(self):
21
+ """Test that UUIDs are normalized."""
22
+ result = normalize_message("Event 550e8400-e29b-41d4-a716-446655440000")
23
+ self.assertEqual(result, "Event UUID")
24
+
25
+ def test_consistent_fingerprints(self):
26
+ """Test that similar errors produce same fingerprint."""
27
+ fp1 = generate_fingerprint(
28
+ "User 123 not found",
29
+ "File app.py:42",
30
+ "backend"
31
+ )
32
+ fp2 = generate_fingerprint(
33
+ "User 456 not found",
34
+ "File app.py:99",
35
+ "backend"
36
+ )
37
+ self.assertEqual(fp1, fp2)
38
+
39
+ def test_different_fingerprints(self):
40
+ """Test that different errors produce different fingerprints."""
41
+ fp1 = generate_fingerprint(
42
+ "User not found",
43
+ "File app.py:42",
44
+ "backend"
45
+ )
46
+ fp2 = generate_fingerprint(
47
+ "Database connection failed",
48
+ "File db.py:10",
49
+ "backend"
50
+ )
51
+ self.assertNotEqual(fp1, fp2)
52
+
53
+
54
+ if __name__ == '__main__':
55
+ unittest.main()
@@ -0,0 +1,22 @@
1
+ """Watchup SDK for Python - Error and event tracking."""
2
+
3
+ __version__ = "1.0.0"
4
+
5
+ from watchup.client import Client, init, capture_error, capture_message, set_user, add_breadcrumb, set_tag, set_tags, set_extra, set_extras, flush, close, configure_scope, capture_errors
6
+
7
+ __all__ = [
8
+ "Client",
9
+ "init",
10
+ "capture_error",
11
+ "capture_message",
12
+ "set_user",
13
+ "add_breadcrumb",
14
+ "set_tag",
15
+ "set_tags",
16
+ "set_extra",
17
+ "set_extras",
18
+ "flush",
19
+ "close",
20
+ "configure_scope",
21
+ "capture_errors",
22
+ ]