logtail-python 0.4.3__tar.gz → 0.5.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 (32) hide show
  1. {logtail_python-0.4.3 → logtail_python-0.5.0}/PKG-INFO +2 -2
  2. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/__init__.py +1 -1
  3. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/compat.py +1 -1
  4. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/flusher.py +13 -12
  5. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/formatter.py +6 -5
  6. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/frame.py +18 -12
  7. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/handler.py +23 -22
  8. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/helpers.py +13 -13
  9. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/rq.py +2 -2
  10. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/uploader.py +6 -5
  11. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/PKG-INFO +2 -2
  12. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/SOURCES.txt +1 -0
  13. {logtail_python-0.4.3 → logtail_python-0.5.0}/setup.py +2 -1
  14. logtail_python-0.5.0/test-requirements.txt +8 -0
  15. logtail_python-0.5.0/tests/__init__.py +0 -0
  16. logtail_python-0.4.3/test-requirements.txt +0 -5
  17. {logtail_python-0.4.3 → logtail_python-0.5.0}/LICENSE.md +0 -0
  18. {logtail_python-0.4.3 → logtail_python-0.5.0}/MANIFEST.in +0 -0
  19. {logtail_python-0.4.3 → logtail_python-0.5.0}/README.md +0 -0
  20. /logtail_python-0.4.3/tests/__init__.py → /logtail_python-0.5.0/logtail/py.typed +0 -0
  21. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/dependency_links.txt +0 -0
  22. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/requires.txt +0 -0
  23. {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/top_level.txt +0 -0
  24. {logtail_python-0.4.3 → logtail_python-0.5.0}/requirements.txt +0 -0
  25. {logtail_python-0.4.3 → logtail_python-0.5.0}/setup.cfg +0 -0
  26. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_flusher.py +0 -0
  27. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_formatter.py +0 -0
  28. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_frame.py +0 -0
  29. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_handler.py +0 -0
  30. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_helpers.py +0 -0
  31. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_rq.py +0 -0
  32. {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_uploader.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logtail-python
3
- Version: 0.4.3
3
+ Version: 0.5.0
4
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.4.3
6
+ Download-URL: https://github.com/logtail/logtail-python/tarball/0.5.0
7
7
  Author: Logtail
8
8
  Author-email: hello@logtail.com
9
9
  License: ISC
@@ -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.4.3'
8
+ __version__ = '0.5.0'
9
9
 
10
10
  context = DEFAULT_CONTEXT
@@ -4,4 +4,4 @@ from __future__ import print_function, unicode_literals
4
4
  try:
5
5
  import queue
6
6
  except ImportError:
7
- import Queue as queue
7
+ import Queue as queue # type: ignore[import-not-found, no-redef]
@@ -3,13 +3,14 @@ from __future__ import print_function, unicode_literals
3
3
 
4
4
  import threading
5
5
  import time
6
+ from typing import Any, Callable, Optional
6
7
 
7
8
  from .compat import queue
8
9
 
9
10
  RETRY_SCHEDULE = (1, 10, 60) # seconds
10
11
 
11
12
 
12
- class TransportFrame(dict):
13
+ class TransportFrame(dict[str, Any]):
13
14
  """A record logged by a flush worker itself, i.e. urllib3 describing our own upload.
14
15
 
15
16
  It rides along with the next batch of regular records but never triggers an upload
@@ -18,7 +19,7 @@ class TransportFrame(dict):
18
19
 
19
20
 
20
21
  class FlushWorker(threading.Thread):
21
- def __init__(self, upload, pipe, buffer_capacity, flush_interval, check_interval):
22
+ def __init__(self, upload: Callable[[list[Any]], Any], pipe: 'queue.Queue[Any]', buffer_capacity: int, flush_interval: float, check_interval: float) -> None:
22
23
  threading.Thread.__init__(self)
23
24
  self.parent_thread = threading.current_thread()
24
25
  self.upload = upload
@@ -30,11 +31,11 @@ class FlushWorker(threading.Thread):
30
31
  self._flushing = False
31
32
  self._clean = True
32
33
 
33
- def run(self):
34
+ def run(self) -> None:
34
35
  while self.should_run:
35
36
  self.step()
36
37
 
37
- def _is_parent_alive(self):
38
+ def _is_parent_alive(self) -> bool:
38
39
  try:
39
40
  return self.parent_thread.is_alive()
40
41
  except RuntimeError:
@@ -44,10 +45,10 @@ class FlushWorker(threading.Thread):
44
45
  # interpreter shutdown). Treat that as "no longer alive".
45
46
  return False
46
47
 
47
- def step(self):
48
+ def step(self) -> None:
48
49
  last_flush = time.time()
49
50
  time_remaining = _initial_time_remaining(self.flush_interval)
50
- frame = []
51
+ frame: list[Any] = []
51
52
  self._clean = True
52
53
 
53
54
  # If the parent thread has exited but there are still outstanding
@@ -80,7 +81,7 @@ class FlushWorker(threading.Thread):
80
81
  # request fails in a way that can be retried, it is retried with an
81
82
  # exponential backoff in between attempts.
82
83
  if frame and not all(isinstance(entry, TransportFrame) for entry in frame):
83
- response = None
84
+ response: Any = None
84
85
  for delay in RETRY_SCHEDULE + (None, ):
85
86
  response = self.upload(frame)
86
87
  if not _should_retry(response.status_code):
@@ -100,7 +101,7 @@ class FlushWorker(threading.Thread):
100
101
  if shutdown and self.pipe.empty():
101
102
  self.should_run = False
102
103
 
103
- def flush(self, timeout=None):
104
+ def flush(self, timeout: Optional[float] = None) -> bool:
104
105
  """Block until the worker has drained the queue.
105
106
 
106
107
  If ``timeout`` is given (in seconds), return after at most that much
@@ -125,19 +126,19 @@ class FlushWorker(threading.Thread):
125
126
  finally:
126
127
  self._flushing = False
127
128
 
128
- def in_flush_worker():
129
+ def in_flush_worker() -> bool:
129
130
  return isinstance(threading.current_thread(), FlushWorker)
130
131
 
131
132
 
132
- def _initial_time_remaining(flush_interval):
133
+ def _initial_time_remaining(flush_interval: float) -> float:
133
134
  return flush_interval
134
135
 
135
136
 
136
- def _calculate_time_remaining(last_flush, flush_interval):
137
+ def _calculate_time_remaining(last_flush: float, flush_interval: float) -> float:
137
138
  elapsed = time.time() - last_flush
138
139
  time_remaining = max(flush_interval - elapsed, 0)
139
140
  return time_remaining
140
141
 
141
142
 
142
- def _should_retry(status_code):
143
+ def _should_retry(status_code: int) -> bool:
143
144
  return 500 <= status_code < 600
@@ -2,21 +2,22 @@
2
2
  from __future__ import print_function, unicode_literals
3
3
  import logging
4
4
  import json
5
+ from typing import Any, Callable, Optional
5
6
 
6
- from .helpers import DEFAULT_CONTEXT
7
+ from .helpers import DEFAULT_CONTEXT, LogtailContext
7
8
  from .frame import create_frame
8
9
 
9
10
 
10
11
  class LogtailFormatter(logging.Formatter):
11
12
  def __init__(self,
12
- context=DEFAULT_CONTEXT,
13
- json_default=None,
14
- json_encoder=None):
13
+ context: LogtailContext = DEFAULT_CONTEXT,
14
+ json_default: Optional[Callable[[Any], Any]] = None,
15
+ json_encoder: Optional[type[json.JSONEncoder]] = None) -> None:
15
16
  self.context = context
16
17
  self.json_default = json_default
17
18
  self.json_encoder = json_encoder
18
19
 
19
- def format(self, record):
20
+ def format(self, record: logging.LogRecord) -> str:
20
21
  # Because the formatter does not have an underlying format string for
21
22
  # which `extra` may be used to substitute arguments (see
22
23
  # https://docs.python.org/2/library/logging.html#logging.debug ), we
@@ -1,24 +1,29 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
  from datetime import datetime, timezone
4
-
4
+ import logging
5
5
  from os import path
6
+ from typing import Any, Optional
6
7
  import __main__
7
8
 
8
- def create_frame(record, message, context, include_extra_attributes=False):
9
+ from .helpers import LogtailContext
10
+
11
+ def create_frame(record: logging.LogRecord, message: str, context: LogtailContext, include_extra_attributes: bool = False) -> dict[str, Any]:
9
12
  r = record.__dict__
10
13
  # Django sends a request object in the record, which is not JSON serializable
11
14
  if "request" in r and not isinstance(r["request"], (dict, list, bool, int, float, str)) :
12
15
  del r["request"]
13
- frame = {}
16
+ frame: dict[str, Any] = {}
14
17
  frame['dt'] = datetime.fromtimestamp(r['created'], timezone.utc).isoformat()
15
18
  frame['level'] = _levelname(r['levelname'])
16
19
  frame['severity'] = int(r['levelno'] / 10)
17
20
  frame['message'] = message
18
- frame['context'] = ctx = {}
21
+ ctx: dict[str, Any] = {}
22
+ frame['context'] = ctx
19
23
 
20
24
  # Runtime context
21
- ctx['runtime'] = runtime = {}
25
+ runtime: dict[str, Any] = {}
26
+ ctx['runtime'] = runtime
22
27
  runtime['function'] = r['funcName']
23
28
  runtime['file'] = _relative_to_main_module_if_possible(r['pathname'])
24
29
  runtime['line'] = r['lineno']
@@ -27,7 +32,8 @@ def create_frame(record, message, context, include_extra_attributes=False):
27
32
  runtime['logger_name'] = r['name']
28
33
 
29
34
  # Runtime context
30
- ctx['system'] = system = {}
35
+ system: dict[str, Any] = {}
36
+ ctx['system'] = system
31
37
  system['pid'] = r['process']
32
38
  system['process_name'] = r['processName']
33
39
 
@@ -41,14 +47,14 @@ def create_frame(record, message, context, include_extra_attributes=False):
41
47
 
42
48
  return _remove_circular_dependencies(frame)
43
49
 
44
- def _parse_custom_events(record, include_extra_attributes):
50
+ def _parse_custom_events(record: logging.LogRecord, include_extra_attributes: bool) -> dict[str, Any]:
45
51
  default_keys = {
46
52
  'args', 'asctime', 'created', 'exc_info', 'exc_text', 'pathname',
47
53
  'funcName', 'levelname', 'levelno', 'lineno', 'module', 'msecs',
48
54
  'message', 'msg', 'name', 'process', 'processName',
49
55
  'relativeCreated', 'thread', 'threadName'
50
56
  }
51
- events = {}
57
+ events: dict[str, Any] = {}
52
58
  for key, val in record.__dict__.items():
53
59
  if key in default_keys:
54
60
  continue
@@ -57,7 +63,7 @@ def _parse_custom_events(record, include_extra_attributes):
57
63
  events[key] = val
58
64
  return events
59
65
 
60
- def _remove_circular_dependencies(obj, memo=None):
66
+ def _remove_circular_dependencies(obj: Any, memo: Optional[set[int]] = None) -> Any:
61
67
  if memo is None:
62
68
  memo = set()
63
69
 
@@ -85,14 +91,14 @@ def _remove_circular_dependencies(obj, memo=None):
85
91
  else:
86
92
  return obj
87
93
 
88
- def _levelname(level):
94
+ def _levelname(level: str) -> str:
89
95
  return level.lower()
90
96
 
91
- def _relative_to_main_module_if_possible(pathname):
97
+ def _relative_to_main_module_if_possible(pathname: str) -> str:
92
98
  has_main_module = hasattr(__main__, '__file__')
93
99
  return _relative_to_main_module(pathname) if has_main_module else pathname
94
100
 
95
- def _relative_to_main_module(pathname):
101
+ def _relative_to_main_module(pathname: str) -> str:
96
102
  try:
97
103
  return path.relpath(pathname, path.dirname(__main__.__file__))
98
104
  except ValueError:
@@ -4,9 +4,10 @@ import logging
4
4
  import json
5
5
  import os
6
6
  import weakref
7
+ from typing import Any, Optional, Union
7
8
 
8
9
  from .compat import queue
9
- from .helpers import DEFAULT_CONTEXT
10
+ from .helpers import DEFAULT_CONTEXT, LogtailContext
10
11
  from .flusher import FlushWorker, TransportFrame, in_flush_worker
11
12
  from .uploader import Uploader
12
13
  from .frame import create_frame
@@ -22,23 +23,23 @@ DEFAULT_TIMEOUT = 30
22
23
  DEFAULT_FLUSH_TIMEOUT = 30
23
24
 
24
25
 
25
- _handlers = weakref.WeakSet()
26
+ _handlers: 'weakref.WeakSet[LogtailHandler]' = weakref.WeakSet()
26
27
 
27
28
 
28
29
  class LogtailHandler(logging.Handler):
29
30
  def __init__(self,
30
- source_token,
31
- host=DEFAULT_HOST,
32
- buffer_capacity=DEFAULT_BUFFER_CAPACITY,
33
- flush_interval=DEFAULT_FLUSH_INTERVAL,
34
- check_interval=DEFAULT_CHECK_INTERVAL,
35
- raise_exceptions=DEFAULT_RAISE_EXCEPTIONS,
36
- drop_extra_events=DEFAULT_DROP_EXTRA_EVENTS,
37
- include_extra_attributes=DEFAULT_INCLUDE_EXTRA_ATTRIBUTES,
38
- context=DEFAULT_CONTEXT,
39
- timeout=DEFAULT_TIMEOUT,
40
- flush_timeout=DEFAULT_FLUSH_TIMEOUT,
41
- level=logging.NOTSET):
31
+ source_token: str,
32
+ host: str = DEFAULT_HOST,
33
+ buffer_capacity: int = DEFAULT_BUFFER_CAPACITY,
34
+ flush_interval: float = DEFAULT_FLUSH_INTERVAL,
35
+ check_interval: float = DEFAULT_CHECK_INTERVAL,
36
+ raise_exceptions: bool = DEFAULT_RAISE_EXCEPTIONS,
37
+ drop_extra_events: bool = DEFAULT_DROP_EXTRA_EVENTS,
38
+ include_extra_attributes: bool = DEFAULT_INCLUDE_EXTRA_ATTRIBUTES,
39
+ context: LogtailContext = DEFAULT_CONTEXT,
40
+ timeout: Union[float, tuple[float, float], None] = DEFAULT_TIMEOUT,
41
+ flush_timeout: Optional[float] = DEFAULT_FLUSH_TIMEOUT,
42
+ level: Union[int, str] = logging.NOTSET) -> None:
42
43
  super(LogtailHandler, self).__init__(level=level)
43
44
  self.source_token = source_token
44
45
  if host.startswith('https://') or host.startswith('http://'):
@@ -46,7 +47,7 @@ class LogtailHandler(logging.Handler):
46
47
  else:
47
48
  self.host = "https://" + host
48
49
  self.context = context
49
- self.pipe = queue.Queue(maxsize=buffer_capacity)
50
+ self.pipe: 'queue.Queue[dict[str, Any]]' = queue.Queue(maxsize=buffer_capacity)
50
51
  self.uploader = Uploader(self.source_token, self.host, timeout)
51
52
  self.drop_extra_events = drop_extra_events
52
53
  self.include_extra_attributes = include_extra_attributes
@@ -57,10 +58,10 @@ class LogtailHandler(logging.Handler):
57
58
  self.flush_timeout = flush_timeout
58
59
  self.dropcount = 0
59
60
  # Do not initialize the flush thread yet because it causes issues on Render.
60
- self.flush_thread = None
61
+ self.flush_thread: Optional[FlushWorker] = None
61
62
  _handlers.add(self)
62
63
 
63
- def ensure_flush_thread_alive(self):
64
+ def ensure_flush_thread_alive(self) -> None:
64
65
  if self.flush_thread and self.flush_thread.is_alive():
65
66
  return
66
67
 
@@ -73,7 +74,7 @@ class LogtailHandler(logging.Handler):
73
74
  )
74
75
  self.flush_thread.start()
75
76
 
76
- def emit(self, record):
77
+ def emit(self, record: logging.LogRecord) -> None:
77
78
  try:
78
79
  self.ensure_flush_thread_alive()
79
80
 
@@ -94,10 +95,10 @@ class LogtailHandler(logging.Handler):
94
95
  if self.raise_exceptions:
95
96
  raise e
96
97
 
97
- def flush(self):
98
+ def flush(self) -> None:
98
99
  if not (self.flush_thread and self.flush_thread.is_alive()):
99
100
  return
100
- if logging._lock._is_owned():
101
+ if logging._lock._is_owned(): # type: ignore[attr-defined] # private, see below
101
102
  # logging.config.dictConfig() flushes the handlers it replaces while holding the global
102
103
  # logging lock, and the worker's upload needs that lock whenever urllib3's logger has a
103
104
  # level-cache miss, so waiting here could only deadlock. Nothing is lost: the worker
@@ -106,7 +107,7 @@ class LogtailHandler(logging.Handler):
106
107
  if not self.flush_thread.flush(timeout=self.flush_timeout):
107
108
  print('Gave up waiting for Better Stack uploads after {}s, logs are still buffered'.format(self.flush_timeout))
108
109
 
109
- def _reset_after_fork(self):
110
+ def _reset_after_fork(self) -> None:
110
111
  # A forked child inherits the parent's queue with whatever was still buffered in it,
111
112
  # a flush thread object whose thread does not exist in the child, and an HTTP session
112
113
  # whose socket it shares with the parent, so it starts over with fresh ones.
@@ -115,7 +116,7 @@ class LogtailHandler(logging.Handler):
115
116
  self.uploader.reset()
116
117
 
117
118
 
118
- def _reset_handlers_after_fork():
119
+ def _reset_handlers_after_fork() -> None:
119
120
  for handler in _handlers:
120
121
  handler._reset_after_fork()
121
122
 
@@ -1,12 +1,14 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
+ from types import TracebackType
4
+ from typing import Any, Optional
3
5
 
4
6
 
5
7
  class LogtailContext(object):
6
- def __init__(self):
7
- self.extras = []
8
+ def __init__(self) -> None:
9
+ self.extras: list[dict[str, dict[str, Any]]] = []
8
10
 
9
- def context(self, *args, **kwargs):
11
+ def context(self, *args: Any, **kwargs: dict[str, Any]) -> 'LogtailContext':
10
12
  if args:
11
13
  raise ValueError(
12
14
  'All contexts must be passed by name as keyword arguments'
@@ -19,23 +21,21 @@ class LogtailContext(object):
19
21
  self.extras.append(kwargs)
20
22
  return self
21
23
 
22
- def __call__(self, *args, **kwargs):
24
+ def __call__(self, *args: Any, **kwargs: dict[str, Any]) -> 'LogtailContext':
23
25
  return self.context(*args, **kwargs)
24
26
 
25
- def __enter__(self):
27
+ def __enter__(self) -> 'LogtailContext':
26
28
  return self
27
29
 
28
- def __exit__(self, type_, value, traceback):
29
- if type_ is not None:
30
- return False
31
- self.extras.pop()
32
- return self
30
+ def __exit__(self, type_: Optional[type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> None:
31
+ if type_ is None:
32
+ self.extras.pop()
33
33
 
34
- def exists(self):
34
+ def exists(self) -> bool:
35
35
  return bool(self.extras)
36
36
 
37
- def collapse(self):
38
- x = {}
37
+ def collapse(self) -> dict[str, dict[str, Any]]:
38
+ x: dict[str, dict[str, Any]] = {}
39
39
  for contexts in self.extras:
40
40
  for name, data in contexts.items():
41
41
  x.setdefault(name, {}).update(data)
@@ -16,7 +16,7 @@ class Worker(rq.Worker):
16
16
  in django-rq.
17
17
  """
18
18
 
19
- def perform_job(self, job, queue):
19
+ def perform_job(self, job: rq.job.Job, queue: rq.Queue) -> bool:
20
20
  try:
21
21
  return super(Worker, self).perform_job(job, queue)
22
22
  finally:
@@ -24,6 +24,6 @@ class Worker(rq.Worker):
24
24
  handler.flush()
25
25
 
26
26
 
27
- def _logtail_handlers():
27
+ def _logtail_handlers() -> set[LogtailHandler]:
28
28
  loggers = [logging.getLogger()] + [logger for logger in logging.Logger.manager.loggerDict.values() if isinstance(logger, logging.Logger)]
29
29
  return {handler for logger in loggers for handler in logger.handlers if isinstance(handler, LogtailHandler)}
@@ -1,17 +1,18 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
  import os
4
+ from typing import Any, Union
4
5
  import msgpack
5
6
  import requests
6
7
  import requests.utils
7
8
 
8
9
  class Fake500(object):
9
- def __init__(self, exception):
10
+ def __init__(self, exception: Exception) -> None:
10
11
  self.status_code = 500
11
12
  self.exception = exception
12
13
 
13
14
  class Uploader(object):
14
- def __init__(self, source_token, host, timeout):
15
+ def __init__(self, source_token: str, host: str, timeout: Union[float, tuple[float, float], None]) -> None:
15
16
  self.source_token = source_token
16
17
  self.host = host
17
18
  self.timeout = timeout
@@ -27,19 +28,19 @@ class Uploader(object):
27
28
  }
28
29
  self.session = self._new_session()
29
30
 
30
- def __call__(self, frame):
31
+ def __call__(self, frame: list[dict[str, Any]]) -> Union[requests.Response, Fake500]:
31
32
  data = msgpack.packb(frame, use_bin_type=True)
32
33
  try:
33
34
  return self.session.post(self.host, data=data, headers=self.headers, timeout=self.timeout)
34
35
  except requests.RequestException as e:
35
36
  return Fake500(e)
36
37
 
37
- def reset(self):
38
+ def reset(self) -> None:
38
39
  # A forked child shares the parent's pooled socket, so it needs a session of its own,
39
40
  # built from the settings resolved above rather than looked up again.
40
41
  self.session = self._new_session()
41
42
 
42
- def _new_session(self):
43
+ def _new_session(self) -> requests.Session:
43
44
  session = requests.Session()
44
45
  session.trust_env = False
45
46
  session.proxies = self.proxies
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logtail-python
3
- Version: 0.4.3
3
+ Version: 0.5.0
4
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.4.3
6
+ Download-URL: https://github.com/logtail/logtail-python/tarball/0.5.0
7
7
  Author: Logtail
8
8
  Author-email: hello@logtail.com
9
9
  License: ISC
@@ -12,6 +12,7 @@ logtail/formatter.py
12
12
  logtail/frame.py
13
13
  logtail/handler.py
14
14
  logtail/helpers.py
15
+ logtail/py.typed
15
16
  logtail/rq.py
16
17
  logtail/uploader.py
17
18
  logtail_python.egg-info/PKG-INFO
@@ -3,7 +3,7 @@ import os
3
3
  from setuptools import setup
4
4
 
5
5
 
6
- VERSION = '0.4.3'
6
+ VERSION = '0.5.0'
7
7
  ROOT_DIR = os.path.dirname(__file__)
8
8
 
9
9
  REQUIREMENTS = [
@@ -18,6 +18,7 @@ setup(
18
18
  name='logtail-python',
19
19
  version=VERSION,
20
20
  packages=['logtail'],
21
+ package_data={'logtail': ['py.typed']},
21
22
  include_package_data=True,
22
23
  license='ISC',
23
24
  description='Better Stack client library',
@@ -0,0 +1,8 @@
1
+ coverage>=3.7.1
2
+ httpretty>=0.9.4
3
+ nose-py3
4
+ mock>=1.0.1
5
+ rq
6
+ mypy>=1.13.0
7
+ msgpack-types>=0.5.0
8
+ types-requests>=2.32.0.20241016
File without changes
@@ -1,5 +0,0 @@
1
- coverage>=3.7.1
2
- httpretty>=0.9.4
3
- nose-py3
4
- mock>=1.0.1
5
- rq
File without changes
File without changes