logtail-python 0.3.3__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {logtail_python-0.3.3/logtail_python.egg-info → logtail_python-0.4.0}/PKG-INFO +21 -7
  2. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/__init__.py +1 -1
  3. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/flusher.py +13 -3
  4. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/handler.py +3 -1
  5. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/uploader.py +4 -3
  6. {logtail_python-0.3.3 → logtail_python-0.4.0/logtail_python.egg-info}/PKG-INFO +21 -7
  7. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail_python.egg-info/SOURCES.txt +9 -1
  8. {logtail_python-0.3.3 → logtail_python-0.4.0}/setup.py +6 -5
  9. logtail_python-0.4.0/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  10. logtail_python-0.4.0/tests/__pycache__/test_flusher.cpython-314-pytest-9.1.1.pyc +0 -0
  11. logtail_python-0.4.0/tests/__pycache__/test_flusher.cpython-314.pyc +0 -0
  12. logtail_python-0.4.0/tests/__pycache__/test_formatter.cpython-314-pytest-9.1.1.pyc +0 -0
  13. logtail_python-0.4.0/tests/__pycache__/test_frame.cpython-314-pytest-9.1.1.pyc +0 -0
  14. logtail_python-0.4.0/tests/__pycache__/test_handler.cpython-314-pytest-9.1.1.pyc +0 -0
  15. logtail_python-0.4.0/tests/__pycache__/test_helpers.cpython-314-pytest-9.1.1.pyc +0 -0
  16. logtail_python-0.4.0/tests/__pycache__/test_uploader.cpython-314-pytest-9.1.1.pyc +0 -0
  17. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/test_flusher.py +35 -4
  18. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/test_frame.py +1 -5
  19. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/test_handler.py +10 -0
  20. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/test_uploader.py +5 -2
  21. {logtail_python-0.3.3 → logtail_python-0.4.0}/LICENSE.md +0 -0
  22. {logtail_python-0.3.3 → logtail_python-0.4.0}/MANIFEST.in +0 -0
  23. {logtail_python-0.3.3 → logtail_python-0.4.0}/README.md +0 -0
  24. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/compat.py +0 -0
  25. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/formatter.py +0 -0
  26. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/frame.py +0 -0
  27. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail/helpers.py +0 -0
  28. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail_python.egg-info/dependency_links.txt +0 -0
  29. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail_python.egg-info/requires.txt +0 -0
  30. {logtail_python-0.3.3 → logtail_python-0.4.0}/logtail_python.egg-info/top_level.txt +0 -0
  31. {logtail_python-0.3.3 → logtail_python-0.4.0}/requirements.txt +0 -0
  32. {logtail_python-0.3.3 → logtail_python-0.4.0}/setup.cfg +0 -0
  33. {logtail_python-0.3.3 → logtail_python-0.4.0}/test-requirements.txt +0 -0
  34. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__init__.py +0 -0
  35. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  36. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/test_flusher.cpython-311.pyc +0 -0
  37. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/test_formatter.cpython-311.pyc +0 -0
  38. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/test_frame.cpython-311.pyc +0 -0
  39. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/test_handler.cpython-311.pyc +0 -0
  40. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/test_helpers.cpython-311.pyc +0 -0
  41. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/__pycache__/test_uploader.cpython-311.pyc +0 -0
  42. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/test_formatter.py +0 -0
  43. {logtail_python-0.3.3 → logtail_python-0.4.0}/tests/test_helpers.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: logtail-python
3
- Version: 0.3.3
4
- Summary: Logtail.com client library
3
+ Version: 0.4.0
4
+ Summary: Better Stack client library
5
5
  Home-page: https://github.com/logtail/logtail-python
6
- Download-URL: https://github.com/logtail/logtail-python/tarball/0.3.3
6
+ Download-URL: https://github.com/logtail/logtail-python/tarball/0.4.0
7
7
  Author: Logtail
8
8
  Author-email: hello@logtail.com
9
9
  License: ISC
@@ -12,17 +12,31 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: ISC License (ISCL)
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.7
16
- Classifier: Programming Language :: Python :: 3.8
17
- Classifier: Programming Language :: Python :: 3.9
18
15
  Classifier: Programming Language :: Python :: 3.10
19
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Programming Language :: Python
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE.md
24
25
  Requires-Dist: requests>=2.18.4
25
26
  Requires-Dist: msgpack>=0.5.6
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: download-url
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
26
40
 
27
41
  # [Better Stack](https://betterstack.com/logs) Python client
28
42
 
@@ -5,6 +5,6 @@ from .handler import LogtailHandler
5
5
  from .helpers import LogtailContext, DEFAULT_CONTEXT
6
6
  from .formatter import LogtailFormatter
7
7
 
8
- __version__ = '0.3.3'
8
+ __version__ = '0.4.0'
9
9
 
10
10
  context = DEFAULT_CONTEXT
@@ -26,6 +26,16 @@ class FlushWorker(threading.Thread):
26
26
  while self.should_run:
27
27
  self.step()
28
28
 
29
+ def _is_parent_alive(self):
30
+ try:
31
+ return self.parent_thread.is_alive()
32
+ except RuntimeError:
33
+ # Starting with Python 3.14, calling is_alive() on an
34
+ # already-terminated thread raises a RuntimeError instead of
35
+ # returning False (see the parent thread being torn down during
36
+ # interpreter shutdown). Treat that as "no longer alive".
37
+ return False
38
+
29
39
  def step(self):
30
40
  last_flush = time.time()
31
41
  time_remaining = _initial_time_remaining(self.flush_interval)
@@ -34,7 +44,7 @@ class FlushWorker(threading.Thread):
34
44
 
35
45
  # If the parent thread has exited but there are still outstanding
36
46
  # events, attempt to send them before exiting.
37
- shutdown = not self.parent_thread.is_alive()
47
+ shutdown = not self._is_parent_alive()
38
48
 
39
49
  # Fill phase: take events out of the queue and group them for sending.
40
50
  # Takes up to `buffer_capacity` events out of the queue and groups them
@@ -54,11 +64,11 @@ class FlushWorker(threading.Thread):
54
64
  except queue.Empty:
55
65
  if shutdown or self._flushing:
56
66
  break
57
- shutdown = not self.parent_thread.is_alive()
67
+ shutdown = not self._is_parent_alive()
58
68
  time_remaining = _calculate_time_remaining(last_flush, self.flush_interval)
59
69
 
60
70
  # Send phase: takes the outstanding events (up to `buffer_capacity`
61
- # count) and sends them to the Logtail endpoint all at once. If the
71
+ # count) and sends them to the Better Stack endpoint all at once. If the
62
72
  # request fails in a way that can be retried, it is retried with an
63
73
  # exponential backoff in between attempts.
64
74
  if frame:
@@ -16,6 +16,7 @@ DEFAULT_CHECK_INTERVAL = 0.1
16
16
  DEFAULT_RAISE_EXCEPTIONS = False
17
17
  DEFAULT_DROP_EXTRA_EVENTS = True
18
18
  DEFAULT_INCLUDE_EXTRA_ATTRIBUTES = True
19
+ DEFAULT_TIMEOUT = 30
19
20
 
20
21
 
21
22
  class LogtailHandler(logging.Handler):
@@ -29,6 +30,7 @@ class LogtailHandler(logging.Handler):
29
30
  drop_extra_events=DEFAULT_DROP_EXTRA_EVENTS,
30
31
  include_extra_attributes=DEFAULT_INCLUDE_EXTRA_ATTRIBUTES,
31
32
  context=DEFAULT_CONTEXT,
33
+ timeout=DEFAULT_TIMEOUT,
32
34
  level=logging.NOTSET):
33
35
  super(LogtailHandler, self).__init__(level=level)
34
36
  self.source_token = source_token
@@ -38,7 +40,7 @@ class LogtailHandler(logging.Handler):
38
40
  self.host = "https://" + host
39
41
  self.context = context
40
42
  self.pipe = queue.Queue(maxsize=buffer_capacity)
41
- self.uploader = Uploader(self.source_token, self.host)
43
+ self.uploader = Uploader(self.source_token, self.host, timeout)
42
44
  self.drop_extra_events = drop_extra_events
43
45
  self.include_extra_attributes = include_extra_attributes
44
46
  self.buffer_capacity = buffer_capacity
@@ -9,9 +9,10 @@ class Fake500(object):
9
9
  self.exception = exception
10
10
 
11
11
  class Uploader(object):
12
- def __init__(self, source_token, host):
12
+ def __init__(self, source_token, host, timeout):
13
13
  self.source_token = source_token
14
14
  self.host = host
15
+ self.timeout = timeout
15
16
  self.session = requests.Session()
16
17
  self.headers = {
17
18
  'Authorization': 'Bearer %s' % source_token,
@@ -21,6 +22,6 @@ class Uploader(object):
21
22
  def __call__(self, frame):
22
23
  data = msgpack.packb(frame, use_bin_type=True)
23
24
  try:
24
- return self.session.post(self.host, data=data, headers=self.headers)
25
+ return self.session.post(self.host, data=data, headers=self.headers, timeout=self.timeout)
25
26
  except requests.RequestException as e:
26
- return Fake500(e)
27
+ return Fake500(e)
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: logtail-python
3
- Version: 0.3.3
4
- Summary: Logtail.com client library
3
+ Version: 0.4.0
4
+ Summary: Better Stack client library
5
5
  Home-page: https://github.com/logtail/logtail-python
6
- Download-URL: https://github.com/logtail/logtail-python/tarball/0.3.3
6
+ Download-URL: https://github.com/logtail/logtail-python/tarball/0.4.0
7
7
  Author: Logtail
8
8
  Author-email: hello@logtail.com
9
9
  License: ISC
@@ -12,17 +12,31 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: ISC License (ISCL)
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.7
16
- Classifier: Programming Language :: Python :: 3.8
17
- Classifier: Programming Language :: Python :: 3.9
18
15
  Classifier: Programming Language :: Python :: 3.10
19
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Programming Language :: Python
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE.md
24
25
  Requires-Dist: requests>=2.18.4
25
26
  Requires-Dist: msgpack>=0.5.6
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: download-url
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
26
40
 
27
41
  # [Better Stack](https://betterstack.com/logs) Python client
28
42
 
@@ -26,9 +26,17 @@ tests/test_handler.py
26
26
  tests/test_helpers.py
27
27
  tests/test_uploader.py
28
28
  tests/__pycache__/__init__.cpython-311.pyc
29
+ tests/__pycache__/__init__.cpython-314.pyc
29
30
  tests/__pycache__/test_flusher.cpython-311.pyc
31
+ tests/__pycache__/test_flusher.cpython-314-pytest-9.1.1.pyc
32
+ tests/__pycache__/test_flusher.cpython-314.pyc
30
33
  tests/__pycache__/test_formatter.cpython-311.pyc
34
+ tests/__pycache__/test_formatter.cpython-314-pytest-9.1.1.pyc
31
35
  tests/__pycache__/test_frame.cpython-311.pyc
36
+ tests/__pycache__/test_frame.cpython-314-pytest-9.1.1.pyc
32
37
  tests/__pycache__/test_handler.cpython-311.pyc
38
+ tests/__pycache__/test_handler.cpython-314-pytest-9.1.1.pyc
33
39
  tests/__pycache__/test_helpers.cpython-311.pyc
34
- tests/__pycache__/test_uploader.cpython-311.pyc
40
+ tests/__pycache__/test_helpers.cpython-314-pytest-9.1.1.pyc
41
+ tests/__pycache__/test_uploader.cpython-311.pyc
42
+ tests/__pycache__/test_uploader.cpython-314-pytest-9.1.1.pyc
@@ -3,7 +3,7 @@ import os
3
3
  from setuptools import setup
4
4
 
5
5
 
6
- VERSION = '0.3.3'
6
+ VERSION = '0.4.0'
7
7
  ROOT_DIR = os.path.dirname(__file__)
8
8
 
9
9
  REQUIREMENTS = [
@@ -20,13 +20,14 @@ setup(
20
20
  packages=['logtail'],
21
21
  include_package_data=True,
22
22
  license='ISC',
23
- description='Logtail.com client library',
23
+ description='Better Stack client library',
24
24
  long_description=long_description,
25
25
  long_description_content_type='text/markdown',
26
26
  url='https://github.com/logtail/logtail-python',
27
27
  download_url='https://github.com/logtail/logtail-python/tarball/%s' % (VERSION),
28
28
  keywords=['api', 'logtail', 'logging', 'client'],
29
29
  install_requires=REQUIREMENTS,
30
+ python_requires='>=3.10',
30
31
  author='Logtail',
31
32
  author_email='hello@logtail.com',
32
33
  classifiers=[
@@ -34,11 +35,11 @@ setup(
34
35
  'License :: OSI Approved :: ISC License (ISCL)',
35
36
  'Operating System :: OS Independent',
36
37
  'Programming Language :: Python :: 3',
37
- 'Programming Language :: Python :: 3.7',
38
- 'Programming Language :: Python :: 3.8',
39
- 'Programming Language :: Python :: 3.9',
40
38
  'Programming Language :: Python :: 3.10',
41
39
  'Programming Language :: Python :: 3.11',
40
+ 'Programming Language :: Python :: 3.12',
41
+ 'Programming Language :: Python :: 3.13',
42
+ 'Programming Language :: Python :: 3.14',
42
43
  'Programming Language :: Python',
43
44
  'Topic :: Software Development :: Libraries :: Python Modules',
44
45
  ],
@@ -1,7 +1,6 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
  import mock
4
- import sys
5
4
  import time
6
5
  import threading
7
6
  import unittest
@@ -20,10 +19,11 @@ class TestFlushWorker(unittest.TestCase):
20
19
  buffer_capacity = 5
21
20
  flush_interval = 2
22
21
  check_interval = 0.01
22
+ timeout = 0.1
23
23
 
24
24
  def _setup_worker(self, uploader=None):
25
25
  pipe = queue.Queue(maxsize=self.buffer_capacity)
26
- uploader = uploader or Uploader(self.source_token, self.host)
26
+ uploader = uploader or Uploader(self.source_token, self.host, self.timeout)
27
27
  fw = FlushWorker(uploader, pipe, self.buffer_capacity, self.flush_interval, self.check_interval)
28
28
  return pipe, uploader, fw
29
29
 
@@ -145,8 +145,39 @@ class TestFlushWorker(unittest.TestCase):
145
145
  self.assertEqual(self.upload_calls, 1)
146
146
  self.assertFalse(fw.should_run)
147
147
 
148
- # test relies on overriding excepthook which is available from 3.8+
149
- @unittest.skipIf(sys.version_info < (3, 8), "Test skipped because overriding excepthook is only available on Python 3.8+")
148
+ def test_is_parent_alive_handles_runtime_error(self):
149
+ # Starting with Python 3.14, is_alive() raises RuntimeError when
150
+ # called on an already-terminated thread instead of returning False.
151
+ # The worker must treat that as the parent no longer being alive.
152
+ _, _, fw = self._setup_worker()
153
+ fw.parent_thread = mock.MagicMock()
154
+ fw.parent_thread.is_alive.side_effect = RuntimeError('cannot join current thread')
155
+
156
+ self.assertFalse(fw._is_parent_alive())
157
+
158
+ def test_shutdown_condition_when_parent_is_alive_raises(self):
159
+ self.buffer_capacity = 10
160
+ num_items = 5
161
+ first_frame = list(range(self.buffer_capacity))
162
+ self.assertLess(num_items, self.buffer_capacity)
163
+
164
+ self.upload_calls = 0
165
+ def uploader(frame):
166
+ self.upload_calls += 1
167
+ self.assertEqual(frame, first_frame[:num_items])
168
+ return mock.MagicMock(status_code=202)
169
+
170
+ pipe, _, fw = self._setup_worker(uploader)
171
+ fw.parent_thread = mock.MagicMock()
172
+ fw.parent_thread.is_alive.side_effect = RuntimeError('cannot join current thread')
173
+
174
+ for i in range(num_items):
175
+ pipe.put(first_frame[i], block=False)
176
+
177
+ fw.step()
178
+ self.assertEqual(self.upload_calls, 1)
179
+ self.assertFalse(fw.should_run)
180
+
150
181
  def test_shutdown_dont_raise_exception_in_thread(self):
151
182
  original_excepthook = threading.excepthook
152
183
  threading.excepthook = mock.Mock()
@@ -3,7 +3,6 @@
3
3
  from logtail.frame import create_frame
4
4
  from logtail.handler import LogtailHandler
5
5
  from logtail.helpers import LogtailContext
6
- from sys import version_info
7
6
  import datetime
8
7
  import unittest
9
8
  import logging
@@ -17,11 +16,8 @@ class TestLogtailLogEntry(unittest.TestCase):
17
16
  # ISO timestamp must end with timezone info
18
17
  self.assertTrue(frame['dt'].endswith("+00:00"))
19
18
 
20
- # These tests require Python >= 3.7
21
- if version_info.major == 2 or version_info.minor <= 6:
22
- return
23
19
  # Sent date matches log record date
24
- date_ref = datetime.datetime.utcfromtimestamp(log_record.created).replace(tzinfo=datetime.timezone.utc)
20
+ date_ref = datetime.datetime.fromtimestamp(log_record.created, datetime.timezone.utc)
25
21
  date_sent = datetime.datetime.fromisoformat(frame['dt'])
26
22
  self.assertEqual(date_ref, date_sent)
27
23
 
@@ -20,6 +20,16 @@ class TestLogtailHandler(unittest.TestCase):
20
20
  handler = LogtailHandler(source_token=self.source_token, host=self.host)
21
21
  self.assertEqual(handler.uploader.source_token, self.source_token)
22
22
  self.assertEqual(handler.uploader.host, "https://" + self.host)
23
+
24
+ @patch('logtail.handler.FlushWorker')
25
+ def test_handler_passes_timeout_to_uploader(self, MockWorker):
26
+ # Test default timeout
27
+ handler = LogtailHandler(source_token=self.source_token, host=self.host)
28
+ self.assertEqual(handler.uploader.timeout, 30)
29
+
30
+ # Test custom timeout
31
+ handler = LogtailHandler(source_token=self.source_token, host=self.host, timeout=10)
32
+ self.assertEqual(handler.uploader.timeout, 10)
23
33
 
24
34
  @patch('logtail.handler.FlushWorker')
25
35
  def test_handler_creates_pipe_from_args(self, MockWorker):
@@ -13,10 +13,11 @@ class TestUploader(unittest.TestCase):
13
13
  host = 'https://in.logtail.com'
14
14
  source_token = 'dummy_source_token'
15
15
  frame = [1, 2, 3]
16
+ timeout = 30
16
17
 
17
18
  @patch('logtail.uploader.requests.Session.post')
18
19
  def test_call(self, post):
19
- def mock_post(endpoint, data=None, headers=None):
20
+ def mock_post(endpoint, data=None, headers=None, timeout=None):
20
21
  # Check that the data is sent to ther correct endpoint
21
22
  self.assertEqual(endpoint, self.host)
22
23
  # Check the content-type
@@ -25,9 +26,11 @@ class TestUploader(unittest.TestCase):
25
26
  self.assertEqual('application/msgpack', headers.get('Content-Type'))
26
27
  # Check the content was msgpacked correctly
27
28
  self.assertEqual(msgpack.unpackb(data, raw=False), self.frame)
29
+ # Check that timeout is passed to the request
30
+ self.assertEqual(timeout, 30)
28
31
 
29
32
  post.side_effect = mock_post
30
- u = Uploader(self.source_token, self.host)
33
+ u = Uploader(self.source_token, self.host, self.timeout)
31
34
  u(self.frame)
32
35
 
33
36
  self.assertTrue(post.called)
File without changes
File without changes