From 8d9a8f37327424324513e887f140d64c6f124569 Mon Sep 17 00:00:00 2001 From: Lukasz Boldys Date: Tue, 27 Jun 2017 11:24:09 -0700 Subject: [PATCH 001/139] Fixing TypeError in error printing --- logdna/logdna.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 70f864b..6984dd7 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -33,7 +33,7 @@ def bufferLog(self, message): if message and message['line']: if self.max_length and len(message['line']) > defaults['MAX_LINE_LENGTH']: message['line'] = message['line'][:defaults['MAX_LINE_LENGTH']] + ' (cut off, too long...)' - print('Line was longer than ' + defaults['MAX_LINE_LENGTH'] + ' chars and was truncated.') + print('Line was longer than {0} chars and was truncated.'.format(defaults['MAX_LINE_LENGTH']) self.bufByteLength += sys.getsizeof(message) self.buf.append(message) From f1056d05e215651be057ea79c8355f2db5f358d0 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 27 Jun 2017 12:15:26 -0700 Subject: [PATCH 002/139] ensure request errors are sent to module logger --- logdna/logdna.py | 3 ++- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 6984dd7..1944c89 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -7,6 +7,7 @@ from threading import Timer from socket import gethostname +logger = logging.getLogger(__name__) class LogDNAHandler(logging.Handler): def __init__(self, token, options={}): @@ -58,7 +59,7 @@ def flush(self): self.flusher.cancel() self.flusher = None except requests.exceptions.RequestException as e: - return + logger.error('Error in request to LogDNA: ' + str(e)) def emit(self, record): record = record.__dict__ diff --git a/setup.py b/setup.py index 03908eb..5ebb335 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.0.7', + version = '1.0.8', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.0.7', + download_url = 'https://github.com/logdna/python/tarball/1.0.8', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 9b60d597e43fee989ad9059a0f699cf4b2f01042 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 27 Jun 2017 12:24:37 -0700 Subject: [PATCH 003/139] latest merge had an error, also change from print to logger debug --- logdna/logdna.py | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 1944c89..1644a2c 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -34,7 +34,7 @@ def bufferLog(self, message): if message and message['line']: if self.max_length and len(message['line']) > defaults['MAX_LINE_LENGTH']: message['line'] = message['line'][:defaults['MAX_LINE_LENGTH']] + ' (cut off, too long...)' - print('Line was longer than {0} chars and was truncated.'.format(defaults['MAX_LINE_LENGTH']) + logger.debug('Line was longer than ' + str(defaults['MAX_LINE_LENGTH']) + ' chars and was truncated.') self.bufByteLength += sys.getsizeof(message) self.buf.append(message) diff --git a/setup.py b/setup.py index 5ebb335..66b7ed6 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.0.8', + version = '1.0.9', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.0.8', + download_url = 'https://github.com/logdna/python/tarball/1.0.9', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From d7ee77c73e5998baf08e9f606ff2a86259227481 Mon Sep 17 00:00:00 2001 From: Lukasz Boldys Date: Wed, 28 Jun 2017 11:46:23 -0700 Subject: [PATCH 004/139] Allow logging format message (so we are good with logging docs) --- logdna/logdna.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 1644a2c..1562eb2 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -62,6 +62,7 @@ def flush(self): logger.error('Error in request to LogDNA: ' + str(e)) def emit(self, record): + msg = self.format(record) record = record.__dict__ opts = {} if 'args' in record: @@ -69,7 +70,7 @@ def emit(self, record): message = { 'hostname' : self.hostname, 'timestamp': int(time.time()), - 'line': record['msg'], + 'line': msg, 'level': record['levelname'] or self.level, 'app': self.app or record['module'] } From 3d211e4c0ba20b70222541bc60789c24ef0f5bf4 Mon Sep 17 00:00:00 2001 From: Brennan Ashton Date: Wed, 28 Jun 2017 23:02:05 -0700 Subject: [PATCH 005/139] Flush logger buffer prior to closing handler When an application terminates any outstanding logs in the log handler buffer will be lost. Make sure that the log buffer is flushed prior to being closed. --- logdna/logdna.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 1644a2c..47b4b19 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -90,4 +90,12 @@ def emit(self, record): self.bufferLog(message) def close(self): - logging.Handler.close(self) + """ + Close the log handler. + + Make sure that the log handler has attempted to flush the log buffer before closing. + """ + try: + self.flush() + finally: + logging.Handler.close(self) From 5eadb3b114cd5fa8656a16602765fe6fbb0ca2fd Mon Sep 17 00:00:00 2001 From: respectus Date: Wed, 5 Jul 2017 11:03:48 -0700 Subject: [PATCH 006/139] Bump version of module based on recent PRs --- setup.py | 4 ++-- test.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 66b7ed6..c17980b 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.0.9', + version = '1.1.0', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.0.9', + download_url = 'https://github.com/logdna/python/tarball/1.1.0', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', diff --git a/test.py b/test.py index 1a67b01..1f3b2ca 100644 --- a/test.py +++ b/test.py @@ -26,6 +26,7 @@ def timeThis(): for x in range(100): log.info('DINGLEBOP ' + str(x)) + log.info('%s before you %s', 'Look', 'Leap') print (timeit.timeit(timeThis, number=2)) From 3025214cf929b0e3e8137dec8fe95ad8d2b37733 Mon Sep 17 00:00:00 2001 From: respectus Date: Thu, 13 Jul 2017 16:15:22 -0700 Subject: [PATCH 007/139] add rlocks so that threads do not drop messages when manipulating self.buf --- logdna/configs.py | 1 + logdna/logdna.py | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/logdna/configs.py b/logdna/configs.py index aa42218..bdfe6b2 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -6,6 +6,7 @@ 'MAX_LINE_LENGTH': 32000, 'MAX_INPUT_LENGTH': 32, 'FLUSH_INTERVAL': 5, + 'FLUSH_NOW': 1, 'FLUSH_BYTE_LIMIT': 1000000, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest' } diff --git a/logdna/logdna.py b/logdna/logdna.py index f422da1..2565921 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -4,7 +4,7 @@ from .configs import defaults import logging import requests - +import threading from threading import Timer from socket import gethostname logger = logging.getLogger(__name__) @@ -12,6 +12,7 @@ class LogDNAHandler(logging.Handler): def __init__(self, token, options={}): self.buf = [] + self.secondary = []; logging.Handler.__init__(self) self.token = token self.hostname = options['hostname'] if 'hostname' in options else gethostname() @@ -29,6 +30,7 @@ def __init__(self, token, options={}): self.url = defaults['LOGDNA_URL'] self.bufByteLength = 0 self.flusher = None + self.lock = threading.RLock() def bufferLog(self, message): if message and message['line']: @@ -36,28 +38,43 @@ def bufferLog(self, message): message['line'] = message['line'][:defaults['MAX_LINE_LENGTH']] + ' (cut off, too long...)' logger.debug('Line was longer than ' + str(defaults['MAX_LINE_LENGTH']) + ' chars and was truncated.') - self.bufByteLength += sys.getsizeof(message) - self.buf.append(message) + self.bufByteLength += sys.getsizeof(message) + # Attempt to acquire lock to write to buf, otherwise write to secondary as flush occurs + if not self.lock.acquire(blocking=False): + self.secondary.append(message) + else: + self.buf.append(message) + self.lock.release(); if self.bufByteLength >= self.flushLimit: self.flush() return - if not self.flusher: - self.flusher = Timer(defaults['FLUSH_INTERVAL'], self.flush) - self.flusher.start() + if not self.flusher: + self.flusher = Timer(defaults['FLUSH_INTERVAL'], self.flush) + self.flusher.start() def flush(self): if not self.buf or len(self.buf) < 0: return data = {'e': 'ls', 'ls': self.buf} try: - resp = requests.post(url=defaults['LOGDNA_URL'], json=data, auth=('user', self.token), params={ 'hostname': self.hostname }, stream=True, timeout=defaults['MAX_REQUEST_TIMEOUT']) - self.buf = [] - self.bufByteLength = 0 - if self.flusher: - self.flusher.cancel() - self.flusher = None + # Ensure we have the lock when flushing + if not self.lock.acquire(blocking=False): + if not self.flusher: + self.flusher = Timer(defaults['FLUSH_NOW'], self.flush) + self.flusher.start() + else: + resp = requests.post(url=defaults['LOGDNA_URL'], json=data, auth=('user', self.token), params={ 'hostname': self.hostname }, stream=True, timeout=defaults['MAX_REQUEST_TIMEOUT']) + self.buf = [] + self.bufByteLength = 0 + if self.flusher: + self.flusher.cancel() + self.flusher = None + self.lock.release(); + # Ensure messages that could've dropped are appended back onto buf + self.buf = self.buf + self.secondary; + self.secondary = []; except requests.exceptions.RequestException as e: logger.error('Error in request to LogDNA: ' + str(e)) @@ -93,7 +110,7 @@ def emit(self, record): def close(self): """ Close the log handler. - + Make sure that the log handler has attempted to flush the log buffer before closing. """ try: From 992fd1b126abc810e0f282bffae8e566064d604b Mon Sep 17 00:00:00 2001 From: respectus Date: Thu, 13 Jul 2017 16:34:50 -0700 Subject: [PATCH 008/139] bump versions for new rlock changes --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c17980b..b8fe597 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.1.0', + version = '1.1.1', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.1.0', + download_url = 'https://github.com/logdna/python/tarball/1.1.1', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 2e02c8a592482d24a311b65f50c72cc58dba52c5 Mon Sep 17 00:00:00 2001 From: respectus Date: Mon, 17 Jul 2017 11:18:27 -0700 Subject: [PATCH 009/139] release lock on request fail --- logdna/logdna.py | 1 + 1 file changed, 1 insertion(+) diff --git a/logdna/logdna.py b/logdna/logdna.py index 2565921..65ca506 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -76,6 +76,7 @@ def flush(self): self.buf = self.buf + self.secondary; self.secondary = []; except requests.exceptions.RequestException as e: + self.lock.release(); logger.error('Error in request to LogDNA: ' + str(e)) def emit(self, record): From 6bdfb16929dcb05214121cf4412155dec8e3baed Mon Sep 17 00:00:00 2001 From: respectus Date: Mon, 17 Jul 2017 11:24:13 -0700 Subject: [PATCH 010/139] bump version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b8fe597..62b2e73 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.1.1', + version = '1.1.2', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.1.1', + download_url = 'https://github.com/logdna/python/tarball/1.1.2', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 20b2eccd4f4f0b6ef2c8246dfba499473f66d46f Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 25 Jul 2017 14:56:53 -0700 Subject: [PATCH 011/139] ensure python module can also pass an environment parameter --- README.md | 20 ++++++++++++++++++++ logdna/logdna.py | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0b4354..b45c15c 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,16 @@ Max Length: `32` The default app passed along with every log sent through this instance. +##### env + +_Optional_ +Type: `String` +Default: `''` +Values: `YourCustomEnv` +Max Length: `32` + +The default env passed along with every log sent through this instance. + ##### hostname _Optional_ @@ -183,6 +193,16 @@ Max Length: `32` The app passed along with this log line. +##### env + +_Optional_ +Type: `String` +Default: `''` +Values: `YourCustomEnv` +Max Length: `32` + +The environment passed with this log line. + ##### meta _Optional_ diff --git a/logdna/logdna.py b/logdna/logdna.py index 65ca506..be1c925 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -18,6 +18,7 @@ def __init__(self, token, options={}): self.hostname = options['hostname'] if 'hostname' in options else gethostname() self.level = options['level'] if 'level' in options else 'info' self.app = options['app'] if 'app' in options else '' + self.env = options['env'] if 'env' in options else '' self.setLevel(logging.DEBUG) self.max_length = True @@ -90,7 +91,8 @@ def emit(self, record): 'timestamp': int(time.time()), 'line': msg, 'level': record['levelname'] or self.level, - 'app': self.app or record['module'] + 'app': self.app or record['module'], + 'env': self.env } if 'level' in opts: message['level'] = opts['level'] @@ -98,6 +100,8 @@ def emit(self, record): message['app'] = opts['app'] if 'hostname' in opts: message['hostname'] = opts['hostname'] + if 'env' in opts: + message['env'] = opts['env'] if 'timestamp' in opts: message['timestamp'] = opts['timestamp'] if 'meta' in opts: From 23e7044a9f8fdc8aaf6512cbbaa175a25584b47e Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 12 Sep 2017 11:57:24 -0700 Subject: [PATCH 012/139] ensure that LogDNA module recovers after internet reconnects --- logdna/logdna.py | 4 ++++ setup.py | 4 ++-- test.py | 23 +++++++++++++++++------ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index be1c925..a6bcac8 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -77,9 +77,13 @@ def flush(self): self.buf = self.buf + self.secondary; self.secondary = []; except requests.exceptions.RequestException as e: + if self.flusher: + self.flusher.cancel() + self.flusher = None self.lock.release(); logger.error('Error in request to LogDNA: ' + str(e)) + def emit(self, record): msg = self.format(record) record = record.__dict__ diff --git a/setup.py b/setup.py index 62b2e73..084d64f 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.1.2', + version = '1.1.4', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.1.2', + download_url = 'https://github.com/logdna/python/tarball/1.1.4', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', diff --git a/test.py b/test.py index 1f3b2ca..ec41470 100644 --- a/test.py +++ b/test.py @@ -5,8 +5,9 @@ import logging import timeit import sys +import time -from logdna import LogDNAHandler +from logdna.logdna import LogDNAHandler # from guppy import hpy # h = hpy() @@ -24,10 +25,20 @@ # Lines will be in order upon refresh def timeThis(): - for x in range(100): - log.info('DINGLEBOP ' + str(x)) - log.info('%s before you %s', 'Look', 'Leap') - -print (timeit.timeit(timeThis, number=2)) + for x in range(7): + log.info('GORPGORP ' + str(x)) + #log.info('%s before you %s', 'Look', 'Leap') + +def main_loop(): + while 1: + print (timeit.timeit(timeThis, number=2)) + time.sleep(10) + +if __name__ == '__main__': + try: + main_loop() + except KeyboardInterrupt: + print >> sys.stderr, '\nExiting by user request.\n' + sys.exit(0) # print h.heap() From 2178be5c5a0528a3cfccff7458e5d59bba23e3f1 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 19 Sep 2017 12:30:38 -0700 Subject: [PATCH 013/139] sanitize meta values that are not JSON serializable, include a reference to the respective key in the meta.__errors.sanitizedKeys array --- README.md | 4 +++- logdna/logdna.py | 19 ++++++++++++++++++- setup.py | 4 ++-- test.py | 44 -------------------------------------------- 4 files changed, 23 insertions(+), 48 deletions(-) delete mode 100644 test.py diff --git a/README.md b/README.md index b45c15c..eb64131 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,9 @@ _Optional_ Type: `JSON` Default: `None` -A meta object for additional metadata about the log line that is passed. +A meta object for additional metadata about the log line that is passed. Please ensure values are JSON serializable, +values that are not JSON serializable will be removed and the respective key will be added to the sanitizedKeys +array for your reference. ##### index_meta diff --git a/logdna/logdna.py b/logdna/logdna.py index a6bcac8..de85db3 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -83,6 +83,23 @@ def flush(self): self.lock.release(); logger.error('Error in request to LogDNA: ' + str(e)) + def isJSONable(self, obj): + try: + json.dumps(obj) + return True + except: + return False + + def sanitizeMeta(self, meta): + keysToSanitize = [] + for key,value in meta.items(): + if not self.isJSONable(value): + keysToSanitize.append(key) + if keysToSanitize: + for key in keysToSanitize: + del meta[key] + meta['__errors'] = { 'sanitizedKeys': keysToSanitize } + return meta def emit(self, record): msg = self.format(record) @@ -110,7 +127,7 @@ def emit(self, record): message['timestamp'] = opts['timestamp'] if 'meta' in opts: if self.index_meta: - message['meta'] = opts['meta'] + message['meta'] = self.sanitizeMeta(opts['meta']) else: message['meta'] = json.dumps(opts['meta']) diff --git a/setup.py b/setup.py index 084d64f..e406bcd 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.1.4', + version = '1.2.0', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.1.4', + download_url = 'https://github.com/logdna/python/tarball/1.2.0', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', diff --git a/test.py b/test.py deleted file mode 100644 index ec41470..0000000 --- a/test.py +++ /dev/null @@ -1,44 +0,0 @@ -''' - An example of how to use the LogDNA Handler -''' - -import logging -import timeit -import sys -import time - -from logdna.logdna import LogDNAHandler - -# from guppy import hpy -# h = hpy() - -key = 'YOUR API KEY HERE' -log = logging.getLogger('logdna') -log.setLevel(logging.INFO) -test = LogDNAHandler(key, { 'hostname': 'pytest' }) - -log.addHandler(test) - -log.warn("Warning message", {'app': 'bloop'}) -log.info("Info message") -# print h.heap() - -# Lines will be in order upon refresh -def timeThis(): - for x in range(7): - log.info('GORPGORP ' + str(x)) - #log.info('%s before you %s', 'Look', 'Leap') - -def main_loop(): - while 1: - print (timeit.timeit(timeThis, number=2)) - time.sleep(10) - -if __name__ == '__main__': - try: - main_loop() - except KeyboardInterrupt: - print >> sys.stderr, '\nExiting by user request.\n' - sys.exit(0) - -# print h.heap() From 3adf75addfe9c2973169c2d3f985b5191ae47d51 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 19 Sep 2017 12:47:55 -0700 Subject: [PATCH 014/139] ensure sanitized keys are added to the __errors object as a string --- README.md | 3 +-- logdna/logdna.py | 2 +- setup.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index eb64131..b18fcc5 100644 --- a/README.md +++ b/README.md @@ -210,8 +210,7 @@ Type: `JSON` Default: `None` A meta object for additional metadata about the log line that is passed. Please ensure values are JSON serializable, -values that are not JSON serializable will be removed and the respective key will be added to the sanitizedKeys -array for your reference. +values that are not JSON serializable will be removed and the respective keys will be added to the `__errors` string. ##### index_meta diff --git a/logdna/logdna.py b/logdna/logdna.py index de85db3..ccf5faa 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -98,7 +98,7 @@ def sanitizeMeta(self, meta): if keysToSanitize: for key in keysToSanitize: del meta[key] - meta['__errors'] = { 'sanitizedKeys': keysToSanitize } + meta['__errors'] = 'These keys have been sanitized: ' + ', '.join(keysToSanitize) return meta def emit(self, record): diff --git a/setup.py b/setup.py index e406bcd..0cb4072 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.0', + version = '1.2.1', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.2.0', + download_url = 'https://github.com/logdna/python/tarball/1.2.1', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From cead1d9222065e38a792803c7fb1c8d124b9e274 Mon Sep 17 00:00:00 2001 From: Jakob de Maeyer Date: Sun, 5 Nov 2017 00:33:18 +0100 Subject: [PATCH 015/139] Use millisecond timestamps --- logdna/logdna.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index ccf5faa..1c255bd 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -109,7 +109,7 @@ def emit(self, record): opts = record['args'] message = { 'hostname' : self.hostname, - 'timestamp': int(time.time()), + 'timestamp': int(time.time() * 1000), 'line': msg, 'level': record['levelname'] or self.level, 'app': self.app or record['module'], From 74b542efc5580cf881964ea4fb6ef7394562b970 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 7 Nov 2017 16:45:40 -0800 Subject: [PATCH 016/139] bump to 1.2.2 ms timestamps --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0cb4072..6aeae39 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.1', + version = '1.2.2', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', From 8f741a4a1f3e43ff3e99822a5d04512adf6a33f1 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 7 Nov 2017 16:48:55 -0800 Subject: [PATCH 017/139] readme updates --- README.md | 116 +++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index b18fcc5..b9ba0ef 100644 --- a/README.md +++ b/README.md @@ -86,12 +86,12 @@ log.info('My Sample Log Line', opts) ## API ### LogDNAHandler(key, [options]) - +--- #### key -_**Required**_ -Type: `String` -Values: `YourAPIKey` +* _**Required**_ +* Type: `String` +* Values: `YourAPIKey` The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your account. @@ -99,39 +99,39 @@ The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your ##### app -_Optional_ -Type: `String` -Default: `''` -Values: `YourCustomApp` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `''` +* Values: `YourCustomApp` +* Max Length: `32` The default app passed along with every log sent through this instance. ##### env -_Optional_ -Type: `String` -Default: `''` -Values: `YourCustomEnv` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `''` +* Values: `YourCustomEnv` +* Max Length: `32` The default env passed along with every log sent through this instance. ##### hostname -_Optional_ -Type: `String` -Default: `''` -Values: `YourCustomHostname` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `''` +* Values: `YourCustomHostname` +* Max Length: `32` The default hostname passed along with every log sent through this instance. ##### index_meta -_Optional_ -Type: `Boolean` -Default: `False` +* _Optional_ +* Type: `Boolean` +* Default: `False` We allow meta objects to be passed with each line. By default these meta objects will be stringified and will not be searchable, but will be displayed for informational purposes. @@ -142,32 +142,32 @@ If this option is turned to true then meta objects will be parsed and will be se ##### level -_Optional_ -Type: `String` -Default: `Info` -Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `Info` +* Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` +* Max Length: `32` The default level passed along with every log sent through this instance. ##### max_length -_Optional_ -Type: `Boolean` -Default: `True` +* _Optional_ +* Type: `Boolean` +* Default: `True` By default the line has a maximum length of 16000 chars, this can be turned off with the value false. ### log(line, [options]) - +--- #### line -_Required_ -Type: `String` -Default: `''` -Max Length: `32000` +* _Required_ +* Type: `String` +* Default: `''` +* Max Length: `32000` The line which will be sent to the LogDNA system. @@ -175,48 +175,48 @@ The line which will be sent to the LogDNA system. ##### level -_Optional_ -Type: `String` -Default: `Info` -Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `Info` +* Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` +* Max Length: `32` The level passed along with this log line. ##### app -_Optional_ -Type: `String` -Default: `''` -Values: `YourCustomApp` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `''` +* Values: `YourCustomApp` +* Max Length: `32` The app passed along with this log line. ##### env -_Optional_ -Type: `String` -Default: `''` -Values: `YourCustomEnv` -Max Length: `32` +* _Optional_ +* Type: `String` +* Default: `''` +* Values: `YourCustomEnv` +* Max Length: `32` The environment passed with this log line. ##### meta -_Optional_ -Type: `JSON` -Default: `None` +* _Optional_ +* Type: `JSON` +* Default: `None` A meta object for additional metadata about the log line that is passed. Please ensure values are JSON serializable, values that are not JSON serializable will be removed and the respective keys will be added to the `__errors` string. ##### index_meta -_Optional_ -Type: `Boolean` -Default: `False` +* _Optional_ +* Type: `Boolean` +* Default: `False` We allow meta objects to be passed with each line. By default these meta objects will be stringified and will not be searchable, but will be displayed for informational purposes. @@ -227,8 +227,8 @@ If this option is turned to true then meta objects will be parsed and will be se ##### timestamp -_Optional_ -Default: `time.time()` +* _Optional_ +* Default: `time.time()` A timestamp in ms, must be within one day otherwise it will be dropped and time.time() will be used in its place. From 1b9867f5ae793adfc3b44e4620e549ba010756cc Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 12 Dec 2017 12:35:39 -0800 Subject: [PATCH 018/139] Readme updates, plus adds the ability to pass ip/mac addresses --- README.md | 4 +++- logdna/logdna.py | 22 ++++++++++++++++++---- setup.py | 4 ++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b9ba0ef..d23275c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ log = logging.getLogger('logdna') log.setLevel(logging.INFO) options = { - 'hostname': 'pytest' + 'hostname': 'pytest', + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE' } # Defaults to false, when true ensures meta object will be searchable diff --git a/logdna/logdna.py b/logdna/logdna.py index 1c255bd..75e94a5 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -5,17 +5,19 @@ import logging import requests import threading +import socket from threading import Timer -from socket import gethostname logger = logging.getLogger(__name__) class LogDNAHandler(logging.Handler): - def __init__(self, token, options={}): + def __init__(self, key, options={}): self.buf = [] self.secondary = []; logging.Handler.__init__(self) - self.token = token + self.key = key self.hostname = options['hostname'] if 'hostname' in options else gethostname() + self.ip = options['ip'] if 'ip' in options else self.get_ip() + self.mac = options['mac'] if 'mac' in options else None self.level = options['level'] if 'level' in options else 'info' self.app = options['app'] if 'app' in options else '' self.env = options['env'] if 'env' in options else '' @@ -66,7 +68,7 @@ def flush(self): self.flusher = Timer(defaults['FLUSH_NOW'], self.flush) self.flusher.start() else: - resp = requests.post(url=defaults['LOGDNA_URL'], json=data, auth=('user', self.token), params={ 'hostname': self.hostname }, stream=True, timeout=defaults['MAX_REQUEST_TIMEOUT']) + resp = requests.post(url=defaults['LOGDNA_URL'], json=data, auth=('user', self.key), params={ 'hostname': self.hostname, 'ip': self.ip, 'mac': self.mac if self.mac else None }, stream=True, timeout=defaults['MAX_REQUEST_TIMEOUT']) self.buf = [] self.bufByteLength = 0 if self.flusher: @@ -133,6 +135,18 @@ def emit(self, record): self.bufferLog(message) + def get_ip(self): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except: + IP = '127.0.0.1' + finally: + s.close() + return IP + def close(self): """ Close the log handler. diff --git a/setup.py b/setup.py index 6aeae39..5370c64 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.2', + version = '1.2.3', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.2.1', + download_url = 'https://github.com/logdna/python/tarball/1.2.3', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From c38c40c9dfaaa902fbcc25b68f55f09bb1036601 Mon Sep 17 00:00:00 2001 From: respectus Date: Thu, 4 Jan 2018 12:02:55 -0800 Subject: [PATCH 019/139] ensure we properly call gethostname from socket --- logdna/logdna.py | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 75e94a5..903bc39 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -15,7 +15,7 @@ def __init__(self, key, options={}): self.secondary = []; logging.Handler.__init__(self) self.key = key - self.hostname = options['hostname'] if 'hostname' in options else gethostname() + self.hostname = options['hostname'] if 'hostname' in options else socket.gethostname() self.ip = options['ip'] if 'ip' in options else self.get_ip() self.mac = options['mac'] if 'mac' in options else None self.level = options['level'] if 'level' in options else 'info' diff --git a/setup.py b/setup.py index 5370c64..b39ce85 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.3', + version = '1.2.4', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.2.3', + download_url = 'https://github.com/logdna/python/tarball/1.2.4', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 74c71636dc071bb97a4915bc95cf16d141d573a7 Mon Sep 17 00:00:00 2001 From: respectus Date: Wed, 7 Mar 2018 17:10:43 -0800 Subject: [PATCH 020/139] the record arg may get mangled by other libs such as botocore, ensure opts object is not mangled from dict to tuple. --- logdna/logdna.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 903bc39..836c0fa 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -117,21 +117,22 @@ def emit(self, record): 'app': self.app or record['module'], 'env': self.env } - if 'level' in opts: - message['level'] = opts['level'] - if 'app' in opts: - message['app'] = opts['app'] - if 'hostname' in opts: - message['hostname'] = opts['hostname'] - if 'env' in opts: - message['env'] = opts['env'] - if 'timestamp' in opts: - message['timestamp'] = opts['timestamp'] - if 'meta' in opts: - if self.index_meta: - message['meta'] = self.sanitizeMeta(opts['meta']) - else: - message['meta'] = json.dumps(opts['meta']) + if not isinstance(opts, tuple): + if 'level' in opts: + message['level'] = opts['level'] + if 'app' in opts: + message['app'] = opts['app'] + if 'hostname' in opts: + message['hostname'] = opts['hostname'] + if 'env' in opts: + message['env'] = opts['env'] + if 'timestamp' in opts: + message['timestamp'] = opts['timestamp'] + if 'meta' in opts: + if self.index_meta: + message['meta'] = self.sanitizeMeta(opts['meta']) + else: + message['meta'] = json.dumps(opts['meta']) self.bufferLog(message) From 41ca42cab2f8215020434bfeb4b668938903346d Mon Sep 17 00:00:00 2001 From: respectus Date: Wed, 7 Mar 2018 17:11:26 -0800 Subject: [PATCH 021/139] bump lib version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b39ce85..efe60ef 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.4', + version = '1.2.5', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', From 39c5decd98e8d4feb6c1bbfa487faf35396c8b12 Mon Sep 17 00:00:00 2001 From: Spain Date: Mon, 16 Apr 2018 15:50:58 -0400 Subject: [PATCH 022/139] feat(handlers): Make available via config file - Add to `logging.handlers` such that LogDNAHandler can be configured via a logging config file --- logdna/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/logdna/__init__.py b/logdna/__init__.py index e617e37..27abc1a 100644 --- a/logdna/__init__.py +++ b/logdna/__init__.py @@ -1,3 +1,8 @@ from .logdna import LogDNAHandler __all__ = ['LogDNAHandler'] +# Publish this class to the "logging.handlers" module so that it can be use +# from a logging config file via logging.config.fileConfig(). +import logging.handlers + +logging.handlers.LogDNAHandler = LogDNAHandler From cf9b505734df12918a665a8a8c74d4fd74e5bc47 Mon Sep 17 00:00:00 2001 From: Spain Date: Mon, 16 Apr 2018 17:30:26 -0400 Subject: [PATCH 023/139] feat(meta): Python log info in meta object - New option `include_standard_meta` which include py log data - Includes `name`, `pathname`, and `lineno` --- README.md | 9 +++++++++ logdna/logdna.py | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d23275c..a3e0459 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,15 @@ The default env passed along with every log sent through this instance. The default hostname passed along with every log sent through this instance. +##### include_standard_meta + +* _Optional_ +* Type: `Boolean` +* Default: `False` + +Python [`LogRecord` objects](https://docs.python.org/2/library/logging.html#logrecord-objects) include language-specific information that may be useful metadata in logs. Setting `include_standard_meta` to `True` will automatically populate meta objects with `name`, `pathname`, and `lineno` from the `LogRecord`. See [`LogRecord` docs](https://docs.python.org/2/library/logging.html#logrecord-objects) for more detail on these values. + + ##### index_meta * _Optional_ diff --git a/logdna/logdna.py b/logdna/logdna.py index 836c0fa..1bb3bc9 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -12,7 +12,7 @@ class LogDNAHandler(logging.Handler): def __init__(self, key, options={}): self.buf = [] - self.secondary = []; + self.secondary = [] logging.Handler.__init__(self) self.key = key self.hostname = options['hostname'] if 'hostname' in options else socket.gethostname() @@ -29,6 +29,9 @@ def __init__(self, key, options={}): self.index_meta = False if 'index_meta' in options: self.index_meta = options['index_meta'] + self.include_standard_meta = False + if 'include_standard_meta' in options: + self.include_standard_meta = options['include_standard_meta'] self.flushLimit = defaults['FLUSH_BYTE_LIMIT'] self.url = defaults['LOGDNA_URL'] self.bufByteLength = 0 @@ -109,8 +112,16 @@ def emit(self, record): opts = {} if 'args' in record: opts = record['args'] + if self.include_standard_meta: + if isinstance(opts, tuple): + opts = {} + if 'meta' not in opts: + opts['meta'] = {} + for key in ['name', 'pathname', 'lineno']: + opts['meta'][key] = record[key] + message = { - 'hostname' : self.hostname, + 'hostname': self.hostname, 'timestamp': int(time.time() * 1000), 'line': msg, 'level': record['levelname'] or self.level, From 16f9bad034a93bd03a16dab995ca8d3e49c139de Mon Sep 17 00:00:00 2001 From: Mike S Date: Tue, 17 Apr 2018 13:43:00 -0400 Subject: [PATCH 024/139] Update README.md --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index d23275c..813c10b 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,40 @@ opts = { log.info('My Sample Log Line', opts) ``` + +### Usage with File Config + +To use `LogDNAHandler` with [`fileConfig`](https://docs.python.org/2/library/logging.config.html#logging.config.fileConfig) (e.g., in a Django `settings.py` file): + +```python +import os +import logging +from logdna import LogDNAHandler # required to register `logging.handlers.LogDNAHandler` + +LOGGING = { + # Other logging settings... + 'handlers': { + 'logdna': { + 'level': logging.DEBUG, + 'class': 'logging.handlers.LogDNAHandler', + 'key': os.environ.get('LOGDNA_INGEST_KEY'), + 'options': { + 'app': '', + 'env': os.environ.get('ENVIRONMENT'), + 'index_meta': , + }, + }, + }, + 'loggers': { + '': { + 'handlers': ['logdna'], + }, + }, +} +``` + +(This example assumes you have set environment variables for `ENVIRONMENT` and `LOGDNA_INGEST_KEY`) + ## API ### LogDNAHandler(key, [options]) From cae0c0351b28b3404b987b444fb532ebfde2dc89 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 17 Apr 2018 11:24:46 -0700 Subject: [PATCH 025/139] bump to 1.2.6 --- LICENSE | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 8c79b7d..0a50bf0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 LogDNA +Copyright (c) 2018 LogDNA Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/setup.py b/setup.py index efe60ef..275abbc 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.5', + version = '1.2.6', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.2.4', + download_url = 'https://github.com/logdna/python/tarball/1.2.6', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 6adc21e872fa521be1aaf08309f7f3d0ba3dc5c5 Mon Sep 17 00:00:00 2001 From: Spain Date: Tue, 17 Apr 2018 17:36:00 -0400 Subject: [PATCH 026/139] feat(tags): Allow tags to be configured - New option `tags` to define logdna host tags --- README.md | 9 +++++++++ logdna/logdna.py | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 786481d..e9386ce 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,15 @@ The default level passed along with every log sent through this instance. By default the line has a maximum length of 16000 chars, this can be turned off with the value false. +##### tags + +* _Optional_ +* Type: `String[]` +* Default: `[]` + +List of tags used to dynamically group hosts. More information on tags is available at [How Do I Use Host Tags?](https://docs.logdna.com/docs/logdna-agent#section-how-do-i-use-host-tags-) + + ### log(line, [options]) --- #### line diff --git a/logdna/logdna.py b/logdna/logdna.py index 1bb3bc9..7d37099 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -23,6 +23,9 @@ def __init__(self, key, options={}): self.env = options['env'] if 'env' in options else '' self.setLevel(logging.DEBUG) + self.tags = [] + if 'tags' in options and isinstance(options['tags'], list): + self.tags.extend(options['tags']) self.max_length = True if 'max_length' in options: self.max_length = options['max_length'] @@ -71,7 +74,17 @@ def flush(self): self.flusher = Timer(defaults['FLUSH_NOW'], self.flush) self.flusher.start() else: - resp = requests.post(url=defaults['LOGDNA_URL'], json=data, auth=('user', self.key), params={ 'hostname': self.hostname, 'ip': self.ip, 'mac': self.mac if self.mac else None }, stream=True, timeout=defaults['MAX_REQUEST_TIMEOUT']) + resp = requests.post( + url=defaults['LOGDNA_URL'], + json=data, + auth=('user', self.key), + params={ + 'hostname': self.hostname, + 'ip': self.ip, + 'mac': self.mac if self.mac else None, + 'tags': self.tags if self.tags else None}, + stream=True, + timeout=defaults['MAX_REQUEST_TIMEOUT']) self.buf = [] self.bufByteLength = 0 if self.flusher: From ec9bb7b1918c8b795ada9aa66f8c275b1d7cc94f Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 17 Apr 2018 14:39:57 -0700 Subject: [PATCH 027/139] bump version 1.2.7, support for tags --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 275abbc..fffb3ee 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,12 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.2.6', + version = '1.2.7', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.2.6', + download_url = 'https://github.com/logdna/python/tarball/1.2.7', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 309eb1dde5d2957388f60fb9abf3ef820dd21c2f Mon Sep 17 00:00:00 2001 From: respectus Date: Mon, 30 Jul 2018 16:40:55 -0700 Subject: [PATCH 028/139] bump the timer down, and allow users to override this option --- README.md | 9 +++++++++ logdna/configs.py | 2 +- logdna/logdna.py | 5 ++++- setup.py | 6 +++--- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e9386ce..d4384bf 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,15 @@ The default level passed along with every log sent through this instance. By default the line has a maximum length of 16000 chars, this can be turned off with the value false. +##### request_timeout + +* _Optional_ +* Type: `int` +* Default: `30000` + +The amount of time the request should wait for LogDNA to respond before timing out. + + ##### tags * _Optional_ diff --git a/logdna/configs.py b/logdna/configs.py index bdfe6b2..3748215 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,6 +1,6 @@ defaults = { 'CONTENT_TYPE': 'application/json; charset=UTF-8', - 'DEFAULT_REQUEST_TIMEOUT': 180000, + 'DEFAULT_REQUEST_TIMEOUT': 30000, 'MS_IN_A_DAY': 86400000, 'MAX_REQUEST_TIMEOUT': 30, 'MAX_LINE_LENGTH': 32000, diff --git a/logdna/logdna.py b/logdna/logdna.py index 7d37099..161cce8 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -40,6 +40,9 @@ def __init__(self, key, options={}): self.bufByteLength = 0 self.flusher = None self.lock = threading.RLock() + self.request_timeout = defaults['MAX_REQUEST_TIMEOUT'] + if 'request_timeout' in options: + self.request_timeout = options['request_timeout'] def bufferLog(self, message): if message and message['line']: @@ -84,7 +87,7 @@ def flush(self): 'mac': self.mac if self.mac else None, 'tags': self.tags if self.tags else None}, stream=True, - timeout=defaults['MAX_REQUEST_TIMEOUT']) + timeout=self.request_timeout) self.buf = [] self.bufByteLength = 0 if self.flusher: diff --git a/setup.py b/setup.py index fffb3ee..00c487f 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ -from distutils.core import setup -setup( +import setuptools +setuptools.setup( name = 'logdna', packages = ['logdna'], - version = '1.2.7', + version = '1.2.8', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', From 6dcd47d29775e3e2e070b4f9f210cce9aabaaba5 Mon Sep 17 00:00:00 2001 From: LY-Huang Date: Tue, 12 Mar 2019 16:29:27 -0700 Subject: [PATCH 029/139] fix recursion error --- README.md | 1 + logdna/logdna.py | 18 ++++++++++++------ setup.py | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4384bf..3a234a2 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ LOGGING = { 'loggers': { '': { 'handlers': ['logdna'], + 'level': logging.DEBUG }, }, } diff --git a/logdna/logdna.py b/logdna/logdna.py index 161cce8..b923640 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -22,6 +22,7 @@ def __init__(self, key, options={}): self.app = options['app'] if 'app' in options else '' self.env = options['env'] if 'env' in options else '' self.setLevel(logging.DEBUG) + self.exceptionFlag = False self.tags = [] if 'tags' in options and isinstance(options['tags'], list): @@ -57,7 +58,7 @@ def bufferLog(self, message): self.secondary.append(message) else: self.buf.append(message) - self.lock.release(); + self.lock.release() if self.bufByteLength >= self.flushLimit: self.flush() return @@ -93,16 +94,21 @@ def flush(self): if self.flusher: self.flusher.cancel() self.flusher = None - self.lock.release(); + self.lock.release() # Ensure messages that could've dropped are appended back onto buf - self.buf = self.buf + self.secondary; - self.secondary = []; + self.buf = self.buf + self.secondary + self.secondary = [] except requests.exceptions.RequestException as e: if self.flusher: self.flusher.cancel() self.flusher = None - self.lock.release(); - logger.error('Error in request to LogDNA: ' + str(e)) + self.lock.release() + if not self.exceptionFlag: + self.exceptionFlag = True + logger.error('Error in request to LogDNA: ' + str(e)) + else: + # when no RequestException happened + self.exceptionFlag = False def isJSONable(self, obj): try: diff --git a/setup.py b/setup.py index 00c487f..b9b3111 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setuptools.setup( name = 'logdna', packages = ['logdna'], - version = '1.2.8', + version = '1.2.9', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', From 4ec419c8c29fbdf959acee76f2bd48f093ca450a Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Tue, 16 Apr 2019 11:59:15 -0400 Subject: [PATCH 030/139] Adding Option to Specify Custom Ingestion Endpoint --- README.md | 7 +++++++ logdna/logdna.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a234a2..776c1e6 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,13 @@ The amount of time the request should wait for LogDNA to respond before timing o List of tags used to dynamically group hosts. More information on tags is available at [How Do I Use Host Tags?](https://docs.logdna.com/docs/logdna-agent#section-how-do-i-use-host-tags-) +##### url + +* _Optional_ +* Type: `String` +* Default: `'https://logs.logdna.com/logs/ingest'` + +The custom ingestion endpoint to stream the log lines into. ### log(line, [options]) --- diff --git a/logdna/logdna.py b/logdna/logdna.py index b923640..2f629ea 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -21,6 +21,7 @@ def __init__(self, key, options={}): self.level = options['level'] if 'level' in options else 'info' self.app = options['app'] if 'app' in options else '' self.env = options['env'] if 'env' in options else '' + self.url = options['url'] if 'url' in options else defaults['LOGDNA_URL'] self.setLevel(logging.DEBUG) self.exceptionFlag = False @@ -37,7 +38,6 @@ def __init__(self, key, options={}): if 'include_standard_meta' in options: self.include_standard_meta = options['include_standard_meta'] self.flushLimit = defaults['FLUSH_BYTE_LIMIT'] - self.url = defaults['LOGDNA_URL'] self.bufByteLength = 0 self.flusher = None self.lock = threading.RLock() @@ -79,7 +79,7 @@ def flush(self): self.flusher.start() else: resp = requests.post( - url=defaults['LOGDNA_URL'], + url=self.url, json=data, auth=('user', self.key), params={ From bbc7ecf977a2ab57ab048499c5652595e0ca8f40 Mon Sep 17 00:00:00 2001 From: respectus Date: Tue, 16 Apr 2019 15:14:59 -0700 Subject: [PATCH 031/139] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b9b3111..abf1eba 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setuptools.setup( name = 'logdna', packages = ['logdna'], - version = '1.2.9', + version = '1.3.0', description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', From eb0191b0168f62c5be048a694be73cd19dc9bdd6 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Tue, 18 Jun 2019 14:17:53 -0400 Subject: [PATCH 032/139] Enable CircleCI Automation (#30) --- .circleci/config.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..4d6de02 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,34 @@ +version: 2 +jobs: + publish: + docker: + - image: circleci/python:3 + steps: + - checkout + - run: + name: Check Tagged Push + command: | + TAG_VERSION=$(git tag -l --sort=-v:refname | head -n 1 | cut -d'-' -f2) + PKG_VERSION=$(cat setup.py | grep "version = " | cut -d'=' -f2 | cut -d"'" -f2) + if [[ "${TAG_VERSION}" != "${PKG_VERSION}" ]]; then + echo "There is mismatch:" + echo " TAG_VERSION: ${TAG_VERSION}" + echo " PKG_VERSION: ${PKG_VERSION}" + exit 1 + fi + - run: + name: Publish + command: | + pip install twine + python setup.py dist + twine upload dist/*.tar.gz +workflows: + version: 2 + update_package: + jobs: + - publish: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?/ + branches: + ignore: /.*/ From 87d6bca008d9288152c50826904381a0ece4d39b Mon Sep 17 00:00:00 2001 From: Dan Maas Date: Wed, 19 Jun 2019 12:10:41 -0700 Subject: [PATCH 033/139] Specify license in setup.py for PyPi index (#22) The `logdna` entry in the Python Package Index shows up without a license because none is specified in setup.py. This patch adds the MIT license identifier to setup.py, --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index abf1eba..3681632 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ description = 'A python package for sending logs to LogDNA', author = 'Answerbook Inc.', author_email = 'help@logdna.com', + license = 'MIT', url = 'https://github.com/logdna/python', download_url = 'https://github.com/logdna/python/tarball/1.2.7', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], From e42c36471b2e8e0f610971e9fbd699e8df285775 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:02:19 -0400 Subject: [PATCH 034/139] 1.4.0: Updates and Fixes (#31) * Make it Consistent * Add Git Ignore * Final Updates! * Updating README * timeout is in seconds * Change in Some Limits --- .gitignore | 1 + README.md | 10 ++++++++++ logdna/configs.py | 7 +++---- logdna/logdna.py | 9 ++++++--- setup.py | 6 +++--- 5 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbfa7d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/*.pyc diff --git a/README.md b/README.md index 776c1e6..f46d451 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ --- +[![CircleCI](https://circleci.com/gh/logdna/python/tree/master.svg?style=svg&circle-token=5b5c0dd0164b684cab6c78c535c5aeed30d613d4)](https://circleci.com/gh/logdna/python/tree/master) + * **[Install](#install)** * **[Setup](#setup)** * **[Usage](#usage)** @@ -196,6 +198,14 @@ If this option is turned to true then meta objects will be parsed and will be se The default level passed along with every log sent through this instance. +##### verbose + +* _Optional_ +* Type: `String` or `Boolean` +* Default: `true` +* Values: False or any level + +The verbosity of the log statements in each failure. ##### max_length diff --git a/logdna/configs.py b/logdna/configs.py index 3748215..b7ae9a4 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,12 +1,11 @@ defaults = { 'CONTENT_TYPE': 'application/json; charset=UTF-8', - 'DEFAULT_REQUEST_TIMEOUT': 30000, - 'MS_IN_A_DAY': 86400000, - 'MAX_REQUEST_TIMEOUT': 30, + 'DEFAULT_REQUEST_TIMEOUT': 30, + 'MS_IN_A_DAY': 24 * 60 * 60 * 1000, 'MAX_LINE_LENGTH': 32000, 'MAX_INPUT_LENGTH': 32, 'FLUSH_INTERVAL': 5, 'FLUSH_NOW': 1, - 'FLUSH_BYTE_LIMIT': 1000000, + 'FLUSH_BYTE_LIMIT': 2 * 1024 * 1024, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest' } diff --git a/logdna/logdna.py b/logdna/logdna.py index 2f629ea..8f7c3b0 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -19,6 +19,7 @@ def __init__(self, key, options={}): self.ip = options['ip'] if 'ip' in options else self.get_ip() self.mac = options['mac'] if 'mac' in options else None self.level = options['level'] if 'level' in options else 'info' + self.verbose = str(options['verbose']).lower() if 'verbose' in options else 'true' self.app = options['app'] if 'app' in options else '' self.env = options['env'] if 'env' in options else '' self.url = options['url'] if 'url' in options else defaults['LOGDNA_URL'] @@ -41,7 +42,7 @@ def __init__(self, key, options={}): self.bufByteLength = 0 self.flusher = None self.lock = threading.RLock() - self.request_timeout = defaults['MAX_REQUEST_TIMEOUT'] + self.request_timeout = defaults['DEFAULT_REQUEST_TIMEOUT'] if 'request_timeout' in options: self.request_timeout = options['request_timeout'] @@ -49,7 +50,8 @@ def bufferLog(self, message): if message and message['line']: if self.max_length and len(message['line']) > defaults['MAX_LINE_LENGTH']: message['line'] = message['line'][:defaults['MAX_LINE_LENGTH']] + ' (cut off, too long...)' - logger.debug('Line was longer than ' + str(defaults['MAX_LINE_LENGTH']) + ' chars and was truncated.') + if self.verbose in ['true', 'debug', 'd']: + logger.debug('Line was longer than ' + str(defaults['MAX_LINE_LENGTH']) + ' chars and was truncated.') self.bufByteLength += sys.getsizeof(message) @@ -105,7 +107,8 @@ def flush(self): self.lock.release() if not self.exceptionFlag: self.exceptionFlag = True - logger.error('Error in request to LogDNA: ' + str(e)) + if self.verbose in ['true', 'error', 'err', 'e']: + logger.error('Error in request to LogDNA: ' + str(e)) else: # when no RequestException happened self.exceptionFlag = False diff --git a/setup.py b/setup.py index 3681632..302d860 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,12 @@ name = 'logdna', packages = ['logdna'], version = '1.3.0', - description = 'A python package for sending logs to LogDNA', - author = 'Answerbook Inc.', + description = 'A Python Package for Sending Logs to LogDNA', + author = 'LogDNA Inc.', author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.2.7', + download_url = 'https://github.com/logdna/python/tarball/1.3.0', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 53b15bad5816469264578b59b98c59612fd78ab1 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:03:09 -0400 Subject: [PATCH 035/139] 1.4.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 302d860..189818e 100644 --- a/setup.py +++ b/setup.py @@ -2,13 +2,13 @@ setuptools.setup( name = 'logdna', packages = ['logdna'], - version = '1.3.0', + version = '1.4.0', description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.3.0', + download_url = 'https://github.com/logdna/python/tarball/1.4.0', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 24c0698a966a90b9456634053a31b57b01791c0c Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:05:41 -0400 Subject: [PATCH 036/139] 1.4.0 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d6de02..e099527 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,7 @@ jobs: - run: name: Publish command: | - pip install twine + sudo pip install twine python setup.py dist twine upload dist/*.tar.gz workflows: From 1d8aac059f243184245444ae313ca194e6a0ced3 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:08:58 -0400 Subject: [PATCH 037/139] 1.4.0 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e099527..d9fbb26 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: name: Publish command: | sudo pip install twine - python setup.py dist + python setup.py sdist twine upload dist/*.tar.gz workflows: version: 2 From e2d2914110e0f5aa63bd000144cbb4efd646971d Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:14:21 -0400 Subject: [PATCH 038/139] Update CircleCI Badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f46d451..4e5e2e0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ --- -[![CircleCI](https://circleci.com/gh/logdna/python/tree/master.svg?style=svg&circle-token=5b5c0dd0164b684cab6c78c535c5aeed30d613d4)](https://circleci.com/gh/logdna/python/tree/master) +[![CircleCI](https://circleci.com/gh/logdna/python.svg?style=svg&circle-token=5b5c0dd0164b684cab6c78c535c5aeed30d613d4)](https://circleci.com/gh/logdna/python) * **[Install](#install)** * **[Setup](#setup)** From 65b96dcf9571f478489aa28007273e90e869ddca Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:14:52 -0400 Subject: [PATCH 039/139] Remove CircleCI Badge --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4e5e2e0..fa28466 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ --- -[![CircleCI](https://circleci.com/gh/logdna/python.svg?style=svg&circle-token=5b5c0dd0164b684cab6c78c535c5aeed30d613d4)](https://circleci.com/gh/logdna/python) - * **[Install](#install)** * **[Setup](#setup)** * **[Usage](#usage)** From c0f99c2e9072ead09b5024fe86544b40785dfd99 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Wed, 19 Jun 2019 16:20:07 -0400 Subject: [PATCH 040/139] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0a50bf0..37e8e3d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 LogDNA +Copyright (c) 2019 LogDNA Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 6506914dc60b8fa433956ee8d225568aa6011a15 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Thu, 20 Jun 2019 16:12:58 -0400 Subject: [PATCH 041/139] Making Requests Stable (#32) * Styling Update * Remove Unused Defaults * Updates... * Update Git Ignore * Request Changes --- .gitignore | 1 + README.md | 9 --- logdna/configs.py | 4 -- logdna/logdna.py | 139 ++++++++++++++++++++-------------------------- logdna/utils.py | 32 +++++++++++ setup.py | 13 ++++- 6 files changed, 103 insertions(+), 95 deletions(-) create mode 100644 logdna/utils.py diff --git a/.gitignore b/.gitignore index fbfa7d1..49d35bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ **/*.pyc +**/*cache* diff --git a/README.md b/README.md index fa28466..3f5d547 100644 --- a/README.md +++ b/README.md @@ -205,15 +205,6 @@ The default level passed along with every log sent through this instance. The verbosity of the log statements in each failure. -##### max_length - -* _Optional_ -* Type: `Boolean` -* Default: `True` - -By default the line has a maximum length of 16000 chars, this can be turned off with the value false. - - ##### request_timeout * _Optional_ diff --git a/logdna/configs.py b/logdna/configs.py index b7ae9a4..2d1f7f5 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,11 +1,7 @@ defaults = { - 'CONTENT_TYPE': 'application/json; charset=UTF-8', 'DEFAULT_REQUEST_TIMEOUT': 30, - 'MS_IN_A_DAY': 24 * 60 * 60 * 1000, 'MAX_LINE_LENGTH': 32000, - 'MAX_INPUT_LENGTH': 32, 'FLUSH_INTERVAL': 5, - 'FLUSH_NOW': 1, 'FLUSH_BYTE_LIMIT': 2 * 1024 * 1024, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest' } diff --git a/logdna/logdna.py b/logdna/logdna.py index 8f7c3b0..e67b8a6 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -1,59 +1,68 @@ import sys import time import json -from .configs import defaults import logging -import requests -import threading import socket -from threading import Timer +import threading +import requests +from requests.adapters import HTTPAdapter +from urllib3.util import Retry +from .configs import defaults +from .utils import sanitize_meta, get_ip + logger = logging.getLogger(__name__) class LogDNAHandler(logging.Handler): def __init__(self, key, options={}): - self.buf = [] - self.secondary = [] logging.Handler.__init__(self) + self.key = key - self.hostname = options['hostname'] if 'hostname' in options else socket.gethostname() - self.ip = options['ip'] if 'ip' in options else self.get_ip() - self.mac = options['mac'] if 'mac' in options else None - self.level = options['level'] if 'level' in options else 'info' - self.verbose = str(options['verbose']).lower() if 'verbose' in options else 'true' - self.app = options['app'] if 'app' in options else '' - self.env = options['env'] if 'env' in options else '' - self.url = options['url'] if 'url' in options else defaults['LOGDNA_URL'] - self.setLevel(logging.DEBUG) - self.exceptionFlag = False - - self.tags = [] - if 'tags' in options and isinstance(options['tags'], list): - self.tags.extend(options['tags']) - self.max_length = True - if 'max_length' in options: - self.max_length = options['max_length'] - self.index_meta = False - if 'index_meta' in options: - self.index_meta = options['index_meta'] - self.include_standard_meta = False - if 'include_standard_meta' in options: - self.include_standard_meta = options['include_standard_meta'] - self.flushLimit = defaults['FLUSH_BYTE_LIMIT'] - self.bufByteLength = 0 + self.buf = [] + self.secondary = [] + self.exception_flag = False + self.buf_byte_length = 0 self.flusher = None + self.max_length = defaults['MAX_LINE_LENGTH'] + self.connection_retries = 5 + self.retry_backoff_factor = 0.5 + + self.hostname = options.get('hostname', socket.gethostname()) + self.ip = options.get('ip', get_ip()) + self.mac = options.get('mac', None) + self.level = options.get('level', 'info') + self.verbose = str(options.get('verbose', 'true')).lower() + self.app = options.get('app', '') + self.env = options.get('env', '') + self.url = options.get('url', defaults['LOGDNA_URL']) + self.request_timeout = options.get('request_timeout', defaults['DEFAULT_REQUEST_TIMEOUT']) + self.include_standard_meta = options.get('include_standard_meta', False) + self.index_meta = options.get('index_meta', False) + self.flush_limit = options.get('flush_limit', defaults['FLUSH_BYTE_LIMIT']) + self.flush_interval = options.get('flush_interval', defaults['FLUSH_INTERVAL']) + self.tags = options.get('tags', []) + + if isinstance(self.tags, str): + self.tags = [tag.strip() for tag in self.tags.split(',')] + elif not isinstance(self.tags, list): + self.tags = [] + + + self.setLevel(logging.DEBUG) self.lock = threading.RLock() - self.request_timeout = defaults['DEFAULT_REQUEST_TIMEOUT'] - if 'request_timeout' in options: - self.request_timeout = options['request_timeout'] - def bufferLog(self, message): + self.session = requests.Session() + retry = Retry(connect=self.connection_retries, backoff_factor=self.retry_backoff_factor) + adapter = HTTPAdapter(max_retries=retry) + self.session.mount('https://', adapter) + + def buffer_log(self, message): if message and message['line']: - if self.max_length and len(message['line']) > defaults['MAX_LINE_LENGTH']: - message['line'] = message['line'][:defaults['MAX_LINE_LENGTH']] + ' (cut off, too long...)' + if len(message['line']) > self.max_length: + message['line'] = message['line'][:self.max_length] + ' (cut off, too long...)' if self.verbose in ['true', 'debug', 'd']: - logger.debug('Line was longer than ' + str(defaults['MAX_LINE_LENGTH']) + ' chars and was truncated.') + logger.debug('Line was longer than %s chars and was truncated.', str(self.max_length)) - self.bufByteLength += sys.getsizeof(message) + self.buf_byte_length += sys.getsizeof(message) # Attempt to acquire lock to write to buf, otherwise write to secondary as flush occurs if not self.lock.acquire(blocking=False): @@ -61,12 +70,12 @@ def bufferLog(self, message): else: self.buf.append(message) self.lock.release() - if self.bufByteLength >= self.flushLimit: + if self.buf_byte_length >= self.flush_limit: self.flush() return if not self.flusher: - self.flusher = Timer(defaults['FLUSH_INTERVAL'], self.flush) + self.flusher = threading.Timer(self.flush_interval, self.flush) self.flusher.start() def flush(self): @@ -77,10 +86,10 @@ def flush(self): # Ensure we have the lock when flushing if not self.lock.acquire(blocking=False): if not self.flusher: - self.flusher = Timer(defaults['FLUSH_NOW'], self.flush) + self.flusher = threading.Timer(1, self.flush) self.flusher.start() else: - resp = requests.post( + self.session.post( url=self.url, json=data, auth=('user', self.key), @@ -92,7 +101,7 @@ def flush(self): stream=True, timeout=self.request_timeout) self.buf = [] - self.bufByteLength = 0 + self.buf_byte_length = 0 if self.flusher: self.flusher.cancel() self.flusher = None @@ -105,31 +114,13 @@ def flush(self): self.flusher.cancel() self.flusher = None self.lock.release() - if not self.exceptionFlag: - self.exceptionFlag = True + if not self.exception_flag: + self.exception_flag = True if self.verbose in ['true', 'error', 'err', 'e']: - logger.error('Error in request to LogDNA: ' + str(e)) + logger.error('Error in Request to LogDNA: %s', str(e)) else: # when no RequestException happened - self.exceptionFlag = False - - def isJSONable(self, obj): - try: - json.dumps(obj) - return True - except: - return False - - def sanitizeMeta(self, meta): - keysToSanitize = [] - for key,value in meta.items(): - if not self.isJSONable(value): - keysToSanitize.append(key) - if keysToSanitize: - for key in keysToSanitize: - del meta[key] - meta['__errors'] = 'These keys have been sanitized: ' + ', '.join(keysToSanitize) - return meta + self.exception_flag = False def emit(self, record): msg = self.format(record) @@ -166,23 +157,11 @@ def emit(self, record): message['timestamp'] = opts['timestamp'] if 'meta' in opts: if self.index_meta: - message['meta'] = self.sanitizeMeta(opts['meta']) + message['meta'] = sanitize_meta(opts['meta']) else: message['meta'] = json.dumps(opts['meta']) - self.bufferLog(message) - - def get_ip(self): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # doesn't even have to be reachable - s.connect(('10.255.255.255', 1)) - IP = s.getsockname()[0] - except: - IP = '127.0.0.1' - finally: - s.close() - return IP + self.buffer_log(message) def close(self): """ diff --git a/logdna/utils.py b/logdna/utils.py new file mode 100644 index 0000000..d94846f --- /dev/null +++ b/logdna/utils.py @@ -0,0 +1,32 @@ +import json +import socket + +def is_jsonable(obj): + try: + json.dumps(obj) + return True + except: + return False + +def sanitize_meta(meta): + keys_to_sanitize = [] + for key, value in meta.items(): + if not is_jsonable(value): + keys_to_sanitize.append(key) + if keys_to_sanitize: + for key in keys_to_sanitize: + del meta[key] + meta['__errors'] = 'These keys have been sanitized: ' + ', '.join(keys_to_sanitize) + return meta + +def get_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + ip = s.getsockname()[0] + except: + ip = '127.0.0.1' + finally: + s.close() + return ip diff --git a/setup.py b/setup.py index 189818e..950908f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,12 @@ -import setuptools -setuptools.setup( +from setuptools import setup +from os import path + +# read the contents of your README file +this_directory = path.abspath(path.dirname(__file__)) +with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +setup( name = 'logdna', packages = ['logdna'], version = '1.4.0', @@ -14,4 +21,6 @@ 'requests', ], classifiers = [], + long_description=long_description, + long_description_content_type='text/markdown', ) From fab6905a71b728a347f22c5dbd3ff5796b0e0d63 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Thu, 20 Jun 2019 16:14:17 -0400 Subject: [PATCH 042/139] 1.4.1 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 950908f..73c8d93 100644 --- a/setup.py +++ b/setup.py @@ -9,13 +9,13 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.4.0', + version = '1.4.1', description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.4.0', + download_url = 'https://github.com/logdna/python/tarball/1.4.1', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 5915698868a4fbf8fbbf2d544aacf5144abb1d87 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Sat, 22 Jun 2019 15:01:12 -0400 Subject: [PATCH 043/139] 1.4.2 --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 73c8d93..bebae8d 100644 --- a/setup.py +++ b/setup.py @@ -3,19 +3,19 @@ # read the contents of your README file this_directory = path.abspath(path.dirname(__file__)) -with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: - long_description = f.read() +with open(path.join(this_directory, 'README.md'), 'rb') as f: + long_description = f.read().decode('utf-8') setup( name = 'logdna', packages = ['logdna'], - version = '1.4.1', + version = '1.4.2', description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.4.1', + download_url = 'https://github.com/logdna/python/tarball/1.4.2', keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From a5a5b2000ba82a9bf5c32b9fa1148a28757df9b7 Mon Sep 17 00:00:00 2001 From: DChai Date: Tue, 13 Aug 2019 14:14:39 -0700 Subject: [PATCH 044/139] Update README.md (#34) Some updates the README. Mostly minor language changes, but also: - Change examples to use `warning` not `warn`; the latter is deprecated in both Python 2 and 3. - Be consistent with `False` and `True` capitalization to match Python type - Fix code examples to use PEP8 indent consistently - Fix doc for `timestamp` - it's in sec, not ms. Or at least `time.time()` returns secs from epoch, not ms. - --- README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 3f5d547..1a65cb2 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ --- -* **[Install](#install)** +* **[Installation](#installation)** * **[Setup](#setup)** * **[Usage](#usage)** * **[API](#api)** * **[License](#license)** -## Install +## Installation ```bash $ pip install logdna @@ -36,14 +36,14 @@ options = { 'mac': 'C0:FF:EE:C0:FF:EE' } -# Defaults to false, when true ensures meta object will be searchable +# Defaults to False; when True meta objects are searchable options['index_meta'] = True test = LogDNAHandler(key, options) log.addHandler(test) -log.warn("Warning message", {'app': 'bloop'}) +log.warning("Warning message", {'app': 'bloop'}) log.info("Info message") ``` @@ -70,17 +70,17 @@ log.info('My Sample Log Line', { 'level': 'MyCustomLevel' }) # Include an App name with this specific log log.info('My Sample Log Line', { 'level': 'Warn', 'app': 'myAppName'}) -# Pass any associated objects along as metadata +# Pass associated objects along as metadata meta = { 'foo': 'bar', 'nested': { - 'nest1': 'nested text' + 'nest1': 'nested text' } } opts = { - 'level': 'warn', - 'meta': meta + 'level': 'warn', + 'meta': meta } log.info('My Sample Log Line', opts) @@ -170,7 +170,7 @@ The default hostname passed along with every log sent through this instance. * Type: `Boolean` * Default: `False` -Python [`LogRecord` objects](https://docs.python.org/2/library/logging.html#logrecord-objects) include language-specific information that may be useful metadata in logs. Setting `include_standard_meta` to `True` will automatically populate meta objects with `name`, `pathname`, and `lineno` from the `LogRecord`. See [`LogRecord` docs](https://docs.python.org/2/library/logging.html#logrecord-objects) for more detail on these values. +Python [`LogRecord` objects](https://docs.python.org/2/library/logging.html#logrecord-objects) includes language-specific information that may be useful metadata in logs. Setting `include_standard_meta` to `True` automatically populates meta objects with `name`, `pathname`, and `lineno` from the `LogRecord`. See [`LogRecord` docs](https://docs.python.org/2/library/logging.html#logrecord-objects) for more details about these values. ##### index_meta @@ -179,11 +179,11 @@ Python [`LogRecord` objects](https://docs.python.org/2/library/logging.html#logr * Type: `Boolean` * Default: `False` -We allow meta objects to be passed with each line. By default these meta objects will be stringified and will not be searchable, but will be displayed for informational purposes. +We allow meta objects to be passed with each line. By default these meta objects are stringified and not searchable, and are only displayed for informational purposes. -If this option is turned to true then meta objects will be parsed and will be searchable up to three levels deep. Any fields deeper than three levels will be stringified and cannot be searched. +If this option is set to True then meta objects are parsed and searchable up to three levels deep. Any fields deeper than three levels are stringified and cannot be searched. -*WARNING* When this option is true, your metadata objects across all types of log messages MUST have consistent types or the metadata object may not be parsed properly! +*WARNING* If this option is True, your metadata objects MUST have consistent types across all log messages or the metadata object may not be parsed properly. ##### level @@ -200,10 +200,10 @@ The default level passed along with every log sent through this instance. * _Optional_ * Type: `String` or `Boolean` -* Default: `true` +* Default: `True` * Values: False or any level -The verbosity of the log statements in each failure. +Sets the verbosity of log statements for failures. ##### request_timeout @@ -211,8 +211,7 @@ The verbosity of the log statements in each failure. * Type: `int` * Default: `30000` -The amount of time the request should wait for LogDNA to respond before timing out. - +The amount of time (in ms) the request should wait for LogDNA to respond before timing out. ##### tags @@ -228,7 +227,7 @@ List of tags used to dynamically group hosts. More information on tags is avail * Type: `String` * Default: `'https://logs.logdna.com/logs/ingest'` -The custom ingestion endpoint to stream the log lines into. +A custom ingestion endpoint to stream log lines into. ### log(line, [options]) --- @@ -239,7 +238,7 @@ The custom ingestion endpoint to stream the log lines into. * Default: `''` * Max Length: `32000` -The line which will be sent to the LogDNA system. +The log line to be sent to LogDNA. #### options @@ -288,8 +287,8 @@ values that are not JSON serializable will be removed and the respective keys wi * Type: `Boolean` * Default: `False` -We allow meta objects to be passed with each line. By default these meta objects will be stringified and will not be searchable, -but will be displayed for informational purposes. +We allow meta objects to be passed with each line. By default these meta objects will be stringified and will not be +searchable, but will be displayed for informational purposes. If this option is turned to true then meta objects will be parsed and will be searchable up to three levels deep. Any fields deeper than three levels will be stringified and cannot be searched. @@ -300,7 +299,7 @@ If this option is turned to true then meta objects will be parsed and will be se * _Optional_ * Default: `time.time()` -A timestamp in ms, must be within one day otherwise it will be dropped and time.time() will be used in its place. +The time in seconds since the epoch to use for the log timestamp. It must be within one day or current time - if it is not, it is ignored and time.time() is used in its place. ## License From 1480c32fbeb0a652cf7af898029dfa2741436ca5 Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Tue, 1 Oct 2019 09:58:43 -0700 Subject: [PATCH 045/139] HTTP Exception Handling (#33) * add tests and fix bugs * fixe tests and internal logging * remove the comment * clean the code * remoive my testFile * adds back off period * after runnign pylint * use python 3 classes in the test * add %d * change debug to print * corrects the logic and cleans the code * change the message * add things to gitignore * correct .gitignore * add build to .gitignore * deleting the files that should've been ignored * remove the egg as well... * dont create a container for failed logs. use buf instead * move the logger to the class * take away the redundant variable * fix the tests as well * bring the var for the buf ize back and create method for calculating the buf size * remove the variable again and calculate the length of message['line'] * address comments and refcator a little bit * remove dubugging prints * rename to snake_case all var in the test file * address all comments excpet the last one. * close regardless of failure to flush * close regardless of failure * fox spaces * missed indent * make the name consistent * change flush_interval too * fix indent --- .gitignore | 8 +++ logdna/configs.py | 6 +- logdna/logdna.py | 146 ++++++++++++++++++++--------------------- logdnaHandlerTest.py | 153 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 238 insertions(+), 75 deletions(-) create mode 100644 logdnaHandlerTest.py diff --git a/.gitignore b/.gitignore index 49d35bf..527a94b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ **/*.pyc **/*cache* +build/ +develop-eggs +dist +src/*.egg-info +logdna.egg-info/ +LogDNAHandler.log +bin +.installed.cfg diff --git a/logdna/configs.py b/logdna/configs.py index 2d1f7f5..04177a9 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,7 +1,9 @@ defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, 'MAX_LINE_LENGTH': 32000, - 'FLUSH_INTERVAL': 5, + 'FLUSH_INTERVAL_SECS': 5, 'FLUSH_BYTE_LIMIT': 2 * 1024 * 1024, - 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest' + 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', + 'BUF_RETENTION_BYTE_LIMIT': 4 * 1024 * 1024, + 'RETRY_INTERVAL_SECS': 8 } diff --git a/logdna/logdna.py b/logdna/logdna.py index e67b8a6..2bab114 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -1,30 +1,33 @@ -import sys -import time import json import logging +import requests import socket +import sys import threading -import requests +import time +from functools import reduce + from requests.adapters import HTTPAdapter from urllib3.util import Retry + from .configs import defaults from .utils import sanitize_meta, get_ip -logger = logging.getLogger(__name__) - class LogDNAHandler(logging.Handler): def __init__(self, key, options={}): logging.Handler.__init__(self) + self.internal_handler = logging.StreamHandler(sys.stdout) + self.internal_handler.setLevel(logging.DEBUG) + self.internalLogger = logging.getLogger('internal') + self.internalLogger.setLevel(logging.DEBUG) + self.internalLogger.addHandler(self.internal_handler) self.key = key self.buf = [] self.secondary = [] self.exception_flag = False - self.buf_byte_length = 0 self.flusher = None self.max_length = defaults['MAX_LINE_LENGTH'] - self.connection_retries = 5 - self.retry_backoff_factor = 0.5 self.hostname = options.get('hostname', socket.gethostname()) self.ip = options.get('ip', get_ip()) @@ -38,89 +41,93 @@ def __init__(self, key, options={}): self.include_standard_meta = options.get('include_standard_meta', False) self.index_meta = options.get('index_meta', False) self.flush_limit = options.get('flush_limit', defaults['FLUSH_BYTE_LIMIT']) - self.flush_interval = options.get('flush_interval', defaults['FLUSH_INTERVAL']) + self.flush_interval_secs = options.get('flush_interval', defaults['FLUSH_INTERVAL_SECS']) + self.retry_interval_secs = options.get('retry_interval_secs', defaults['RETRY_INTERVAL_SECS']) self.tags = options.get('tags', []) + self.buf_retention_byte_limit = options.get('buf_retention_limit', defaults['BUF_RETENTION_BYTE_LIMIT']) if isinstance(self.tags, str): self.tags = [tag.strip() for tag in self.tags.split(',')] elif not isinstance(self.tags, list): self.tags = [] - - self.setLevel(logging.DEBUG) self.lock = threading.RLock() - self.session = requests.Session() - retry = Retry(connect=self.connection_retries, backoff_factor=self.retry_backoff_factor) - adapter = HTTPAdapter(max_retries=retry) - self.session.mount('https://', adapter) - def buffer_log(self, message): if message and message['line']: if len(message['line']) > self.max_length: message['line'] = message['line'][:self.max_length] + ' (cut off, too long...)' if self.verbose in ['true', 'debug', 'd']: - logger.debug('Line was longer than %s chars and was truncated.', str(self.max_length)) - - self.buf_byte_length += sys.getsizeof(message) + self.internalLogger.debug('Line was longer than %s chars and was truncated.', self.max_length) # Attempt to acquire lock to write to buf, otherwise write to secondary as flush occurs - if not self.lock.acquire(blocking=False): - self.secondary.append(message) - else: - self.buf.append(message) + if self.lock.acquire(blocking=False): + buf_size = reduce(lambda x, y: x + len(y['line']), self.buf, 0) + + if buf_size + len(message['line']) < self.buf_retention_byte_limit: + self.buf.append(message) + else: + self.internalLogger.debug('The buffer size exceeded the limit: %s', self.buf_retention_byte_limit) self.lock.release() - if self.buf_byte_length >= self.flush_limit: + + if buf_size >= self.flush_limit and not self.exception_flag: self.flush() - return + else: + self.secondary.append(message) if not self.flusher: - self.flusher = threading.Timer(self.flush_interval, self.flush) + interval = self.retry_interval_secs if self.exception_flag else self.flush_interval_secs + self.flusher = threading.Timer(interval, self.flush) self.flusher.start() - def flush(self): - if not self.buf or len(self.buf) < 0: - return + def clean_after_success(self): + del self.buf[:] + self.exception_flag = False + if self.flusher: + self.flusher.cancel() + self.flusher = None + + def handle_exception(self, exception): + if self.flusher: + self.flusher.cancel() + self.flusher = None + self.exception_flag = True + if self.verbose in ['true', 'error', 'err', 'e']: + self.internalLogger.debug('Error sending logs %s', exception) + + # do not call without acquiring the lock + def send_request(self): + self.buf.extend(self.secondary) + self.secondary = [] data = {'e': 'ls', 'ls': self.buf} try: - # Ensure we have the lock when flushing - if not self.lock.acquire(blocking=False): - if not self.flusher: - self.flusher = threading.Timer(1, self.flush) - self.flusher.start() - else: - self.session.post( - url=self.url, - json=data, - auth=('user', self.key), - params={ - 'hostname': self.hostname, - 'ip': self.ip, - 'mac': self.mac if self.mac else None, - 'tags': self.tags if self.tags else None}, - stream=True, - timeout=self.request_timeout) - self.buf = [] - self.buf_byte_length = 0 - if self.flusher: - self.flusher.cancel() - self.flusher = None - self.lock.release() - # Ensure messages that could've dropped are appended back onto buf - self.buf = self.buf + self.secondary - self.secondary = [] + res = requests.post( + url=self.url, + json=data, + auth=('user', self.key), + params={ + 'hostname': self.hostname, + 'ip': self.ip, + 'mac': self.mac if self.mac else None, + 'tags': self.tags if self.tags else None}, + stream=True, + timeout=self.request_timeout) + res.raise_for_status() + # when no RequestException happened + self.clean_after_success() except requests.exceptions.RequestException as e: - if self.flusher: - self.flusher.cancel() - self.flusher = None + self.handle_exception(e) + + def flush(self): + if len(self.buf) == 0: + return + if self.lock.acquire(blocking=False): + self.send_request() self.lock.release() - if not self.exception_flag: - self.exception_flag = True - if self.verbose in ['true', 'error', 'err', 'e']: - logger.error('Error in Request to LogDNA: %s', str(e)) else: - # when no RequestException happened - self.exception_flag = False + if not self.flusher: + self.flusher = threading.Timer(1, self.flush) + self.flusher.start() def emit(self, record): msg = self.format(record) @@ -160,16 +167,9 @@ def emit(self, record): message['meta'] = sanitize_meta(opts['meta']) else: message['meta'] = json.dumps(opts['meta']) - self.buffer_log(message) def close(self): - """ - Close the log handler. - - Make sure that the log handler has attempted to flush the log buffer before closing. - """ - try: + if len(self.buf) > 0: self.flush() - finally: - logging.Handler.close(self) + logging.Handler.close(self) diff --git a/logdnaHandlerTest.py b/logdnaHandlerTest.py new file mode 100644 index 0000000..3c2903b --- /dev/null +++ b/logdnaHandlerTest.py @@ -0,0 +1,153 @@ +import concurrent.futures +import json +import threading +import time + +from http.server import BaseHTTPRequestHandler,HTTPServer +import logging +import unittest + +from logdna import LogDNAHandler + +current_milli_time = lambda: int(round(time.time() * 1000)) + +key = '< YOUR INGESTION KEY HERE >' +log = logging.getLogger('logdna') +log.setLevel(logging.INFO) + +options = { + 'hostname': 'localhost', + 'url': 'http://localhost:8081', + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE' +} + +expectedLines = [] +class successful_RequestHandler(BaseHTTPRequestHandler): + def do_POST(self): + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + self.send_response(200) + + self.end_headers() + body = json.loads(body)['ls'] + for keys in body: + expectedLines.append(keys['line']) + +class failed_RequestHandler(BaseHTTPRequestHandler): + def do_POST(self): + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + self.send_response(400) + self.end_headers() + +class LogDNAHandlerTest(unittest.TestCase): + def server_recieves_messages(self): + options = { + 'hostname': 'localhost', + 'url': 'http://localhost:8081', + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE' + } + + server_address = ('localhost', 8081) + httpd = HTTPServer(server_address, successful_RequestHandler) + + test = LogDNAHandler(key, options) + log.addHandler(test) + line = "python python python" + + def send_log(): + log.info(line) + + server_thread = threading.Thread(target=httpd.handle_request) + logdna_thread = threading.Thread(target=send_log) + server_thread.daemon = True + logdna_thread.daemon = True + + server_thread.start() + logdna_thread.start() + + server_thread.join() + logdna_thread.join() + + self.assertEqual(len(expectedLines), 1) + self.assertIn(line, expectedLines) + + def messages_preserved_if_excp(self): + options = { + 'hostname': 'localhost', + 'url': 'http://localhost:8080', + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE' + } + server_address = ('localhost', 8080) + httpd = HTTPServer(server_address, failed_RequestHandler) + + failed_case_logger = LogDNAHandler(key, options) + log.addHandler(failed_case_logger) + line = "second test. server fails" + + def send_log_to_fail(): + log.info(line) + + server_thread = threading.Thread(target=httpd.handle_request) + logdna_thread = threading.Thread(target=send_log_to_fail) + server_thread.daemon = True + logdna_thread.daemon = True + + server_thread.start() + logdna_thread.start() + + server_thread.join() + logdna_thread.join() + self.assertEqual(len(failed_case_logger.buf), 1) + + + def stops_retention_when_buf_is_full(self): + options = { + 'hostname': 'localhost', + 'url': 'http://localhost:1337', + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE', + 'buf_retention_limit': 50, + 'equest_timeout': 10, + 'flush_interval': 1, + 'retry_interval_secs': 1 + } + server_address = ('localhost', 1337) + + httpd = HTTPServer(server_address, failed_RequestHandler) + + failed_case_logger = LogDNAHandler(key, options) + log.addHandler(failed_case_logger) + line = "when buffer grows bigger than we want" + lineTwo = "when buffer grows bigger than we want. And more and more" + + def send_log_to_fail(): + log.info(line) + log.info(lineTwo) + + + server_thread = threading.Thread(target=httpd.handle_request) + logdna_thread = threading.Thread(target=send_log_to_fail) + server_thread.daemon = True + logdna_thread.daemon = True + + server_thread.start() + logdna_thread.start() + + server_thread.join() + logdna_thread.join() + + self.assertEqual(len(failed_case_logger.buf), 1) + self.assertNotEqual(failed_case_logger.buf[0]['line'], lineTwo) + + def test_run_tests(self): + self.server_recieves_messages() + self.messages_preserved_if_excp() + self.stops_retention_when_buf_is_full() + + +if __name__ == '__main__': + unittest.main() From f20a5370065dcf1c5bdcdab698b210262865178c Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Mon, 14 Oct 2019 12:47:01 -0700 Subject: [PATCH 046/139] Update User-Agent headers with the current package version info (#36) * add headrs with pkg version * removet he extra space * remove platoform info * changes * move to the _version.py file * fogrot to update the setup file * use lower-case * change the name * change --- _version.py | 1 + logdna/configs.py | 5 ++++- logdna/logdna.py | 5 ++++- logdnaHandlerTest.py | 1 + setup.py | 3 ++- 5 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 _version.py diff --git a/_version.py b/_version.py new file mode 100644 index 0000000..98d186b --- /dev/null +++ b/_version.py @@ -0,0 +1 @@ +__version__ = '1.4.2' diff --git a/logdna/configs.py b/logdna/configs.py index 04177a9..55b0830 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,3 +1,5 @@ +from _version import __version__ + defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, 'MAX_LINE_LENGTH': 32000, @@ -5,5 +7,6 @@ 'FLUSH_BYTE_LIMIT': 2 * 1024 * 1024, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', 'BUF_RETENTION_BYTE_LIMIT': 4 * 1024 * 1024, - 'RETRY_INTERVAL_SECS': 8 + 'RETRY_INTERVAL_SECS': 8, + 'USER_AGENT': 'python/%s' % __version__ } diff --git a/logdna/logdna.py b/logdna/logdna.py index 2bab114..a07aec8 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -45,6 +45,7 @@ def __init__(self, key, options={}): self.retry_interval_secs = options.get('retry_interval_secs', defaults['RETRY_INTERVAL_SECS']) self.tags = options.get('tags', []) self.buf_retention_byte_limit = options.get('buf_retention_limit', defaults['BUF_RETENTION_BYTE_LIMIT']) + self.user_agent = options.get('user_agent', defaults['USER_AGENT']) if isinstance(self.tags, str): self.tags = [tag.strip() for tag in self.tags.split(',')] @@ -111,7 +112,9 @@ def send_request(self): 'mac': self.mac if self.mac else None, 'tags': self.tags if self.tags else None}, stream=True, - timeout=self.request_timeout) + timeout=self.request_timeout, + headers={'user-agent': self.user_agent} + ) res.raise_for_status() # when no RequestException happened self.clean_after_success() diff --git a/logdnaHandlerTest.py b/logdnaHandlerTest.py index 3c2903b..3cc0c28 100644 --- a/logdnaHandlerTest.py +++ b/logdnaHandlerTest.py @@ -25,6 +25,7 @@ expectedLines = [] class successful_RequestHandler(BaseHTTPRequestHandler): def do_POST(self): + content_length = int(self.headers['Content-Length']) body = self.rfile.read(content_length) self.send_response(200) diff --git a/setup.py b/setup.py index bebae8d..95d5154 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ from setuptools import setup from os import path +from _version import __version__ # read the contents of your README file this_directory = path.abspath(path.dirname(__file__)) @@ -9,7 +10,7 @@ setup( name = 'logdna', packages = ['logdna'], - version = '1.4.2', + version = __version__, description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', author_email = 'help@logdna.com', From 490c5159958cf91c9256d8ec3094dae7d5046eec Mon Sep 17 00:00:00 2001 From: Vilya Levitskiy Date: Thu, 5 Dec 2019 13:36:56 -0800 Subject: [PATCH 047/139] bump version --- _version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_version.py b/_version.py index 98d186b..77f1c8e 100644 --- a/_version.py +++ b/_version.py @@ -1 +1 @@ -__version__ = '1.4.2' +__version__ = '1.5.0' From 7ded19b1bc627f586abb8d919d495fa0eede77e4 Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 5 Dec 2019 13:46:52 -0800 Subject: [PATCH 048/139] read the vesrion from the _version file (#37) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d9fbb26..447aa54 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: name: Check Tagged Push command: | TAG_VERSION=$(git tag -l --sort=-v:refname | head -n 1 | cut -d'-' -f2) - PKG_VERSION=$(cat setup.py | grep "version = " | cut -d'=' -f2 | cut -d"'" -f2) + PKG_VERSION=$(cat _version.py | grep "__version__ = " | cut -d'=' -f2 | cut -d"'" -f2) if [[ "${TAG_VERSION}" != "${PKG_VERSION}" ]]; then echo "There is mismatch:" echo " TAG_VERSION: ${TAG_VERSION}" From d27c65eb8d0b9b485c44d8d8f7390b658d16c9bd Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 5 Dec 2019 13:58:17 -0800 Subject: [PATCH 049/139] change the url as well (#38) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 95d5154..5b1dde2 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = 'https://github.com/logdna/python/tarball/1.4.2', + download_url = ('https://github.com/logdna/python/tarball/%s' %(__version__)), keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 119aef19df7cdaa191101e58403b04c8320c085e Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 5 Dec 2019 14:37:17 -0800 Subject: [PATCH 050/139] bump version (#39) --- _version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_version.py b/_version.py index 77f1c8e..51ed7c4 100644 --- a/_version.py +++ b/_version.py @@ -1 +1 @@ -__version__ = '1.5.0' +__version__ = '1.5.1' From 5f77bdc6d95cbb0562cd52282bae0ff080434afd Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 5 Dec 2019 17:00:25 -0800 Subject: [PATCH 051/139] Fix dependency bug (#40) * fix the version bug * remove not used lines * remove unused lines * remove unused lines --- _version.py | 1 - logdna/_version.py | 1 + logdna/configs.py | 4 ++-- logdna/logdna.py | 1 - setup.py | 7 ++++--- 5 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 _version.py create mode 100644 logdna/_version.py diff --git a/_version.py b/_version.py deleted file mode 100644 index 51ed7c4..0000000 --- a/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '1.5.1' diff --git a/logdna/_version.py b/logdna/_version.py new file mode 100644 index 0000000..c3b3841 --- /dev/null +++ b/logdna/_version.py @@ -0,0 +1 @@ +__version__ = '1.5.2' diff --git a/logdna/configs.py b/logdna/configs.py index 55b0830..cdb756a 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,4 +1,4 @@ -from _version import __version__ +import _version defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, @@ -8,5 +8,5 @@ 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', 'BUF_RETENTION_BYTE_LIMIT': 4 * 1024 * 1024, 'RETRY_INTERVAL_SECS': 8, - 'USER_AGENT': 'python/%s' % __version__ + 'USER_AGENT': 'python/%s' % _version.__version__ } diff --git a/logdna/logdna.py b/logdna/logdna.py index a07aec8..b1a3132 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -9,7 +9,6 @@ from requests.adapters import HTTPAdapter from urllib3.util import Retry - from .configs import defaults from .utils import sanitize_meta, get_ip diff --git a/setup.py b/setup.py index 5b1dde2..eddcfa3 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,23 @@ from setuptools import setup from os import path -from _version import __version__ # read the contents of your README file this_directory = path.abspath(path.dirname(__file__)) +import logdna._version as _version + with open(path.join(this_directory, 'README.md'), 'rb') as f: long_description = f.read().decode('utf-8') setup( name = 'logdna', packages = ['logdna'], - version = __version__, + version = _version.__version__, description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = ('https://github.com/logdna/python/tarball/%s' %(__version__)), + download_url = ('https://github.com/logdna/python/tarball/%s' %(_version.__version__)), keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 71420ede8c91259391cfb0fd2a9af44d7a07e254 Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 5 Dec 2019 17:08:13 -0800 Subject: [PATCH 052/139] Change the file in circleci as well (#41) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 447aa54..fbf82c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: name: Check Tagged Push command: | TAG_VERSION=$(git tag -l --sort=-v:refname | head -n 1 | cut -d'-' -f2) - PKG_VERSION=$(cat _version.py | grep "__version__ = " | cut -d'=' -f2 | cut -d"'" -f2) + PKG_VERSION=$(cat logdna/_version.py | grep "__version__ = " | cut -d'=' -f2 | cut -d"'" -f2) if [[ "${TAG_VERSION}" != "${PKG_VERSION}" ]]; then echo "There is mismatch:" echo " TAG_VERSION: ${TAG_VERSION}" From b48ef31b3fa3458cafd0f5f3d885b23273a79811 Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 5 Dec 2019 17:23:15 -0800 Subject: [PATCH 053/139] change the import (#42) --- logdna/configs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logdna/configs.py b/logdna/configs.py index cdb756a..1a54bb5 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,4 +1,4 @@ -import _version +from . import _version defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, From a89b0c2c12d70d7504a431933837e142404192ca Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 12 Dec 2019 14:29:13 -0800 Subject: [PATCH 054/139] Don't import package files in setup (#45) * Don't import the package files * remove the debugging vars * change _version to VERSION file * leave only the numbe --- .circleci/config.yml | 2 +- logdna/VERSION | 1 + logdna/_version.py | 1 - logdna/configs.py | 7 +++++-- setup.py | 8 +++++--- 5 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 logdna/VERSION delete mode 100644 logdna/_version.py diff --git a/.circleci/config.yml b/.circleci/config.yml index fbf82c5..f895176 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: name: Check Tagged Push command: | TAG_VERSION=$(git tag -l --sort=-v:refname | head -n 1 | cut -d'-' -f2) - PKG_VERSION=$(cat logdna/_version.py | grep "__version__ = " | cut -d'=' -f2 | cut -d"'" -f2) + PKG_VERSION=$(cat logdna/VERSION) if [[ "${TAG_VERSION}" != "${PKG_VERSION}" ]]; then echo "There is mismatch:" echo " TAG_VERSION: ${TAG_VERSION}" diff --git a/logdna/VERSION b/logdna/VERSION new file mode 100644 index 0000000..4cda8f1 --- /dev/null +++ b/logdna/VERSION @@ -0,0 +1 @@ +1.5.2 diff --git a/logdna/_version.py b/logdna/_version.py deleted file mode 100644 index c3b3841..0000000 --- a/logdna/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '1.5.2' diff --git a/logdna/configs.py b/logdna/configs.py index 1a54bb5..c36c75a 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,4 +1,7 @@ -from . import _version +from os import path + +with open("%s/VERSION" % path.abspath(path.dirname(__file__))) as f: + version = f.read() defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, @@ -8,5 +11,5 @@ 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', 'BUF_RETENTION_BYTE_LIMIT': 4 * 1024 * 1024, 'RETRY_INTERVAL_SECS': 8, - 'USER_AGENT': 'python/%s' % _version.__version__ + 'USER_AGENT': 'python/%s' % version } diff --git a/setup.py b/setup.py index eddcfa3..cd4c5d4 100644 --- a/setup.py +++ b/setup.py @@ -3,21 +3,23 @@ # read the contents of your README file this_directory = path.abspath(path.dirname(__file__)) -import logdna._version as _version with open(path.join(this_directory, 'README.md'), 'rb') as f: long_description = f.read().decode('utf-8') +with open("%s/logdna/VERSION" % this_directory) as f: + version = f.read() + setup( name = 'logdna', packages = ['logdna'], - version = _version.__version__, + version = version, description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', author_email = 'help@logdna.com', license = 'MIT', url = 'https://github.com/logdna/python', - download_url = ('https://github.com/logdna/python/tarball/%s' %(_version.__version__)), + download_url = ('https://github.com/logdna/python/tarball/%s' %(version)), keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], install_requires=[ 'requests', From 78823842a39f155be36287a0f07f0d3666fbf188 Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 12 Dec 2019 16:24:51 -0800 Subject: [PATCH 055/139] Include static files to the packages. (#46) * include static files and trim the new line * clean * remove MANIFEST. works without it --- logdna/configs.py | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/logdna/configs.py b/logdna/configs.py index c36c75a..3ab9e55 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,7 +1,7 @@ from os import path with open("%s/VERSION" % path.abspath(path.dirname(__file__))) as f: - version = f.read() + version = f.read().strip('\n') defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, diff --git a/setup.py b/setup.py index cd4c5d4..cb3e933 100644 --- a/setup.py +++ b/setup.py @@ -8,11 +8,12 @@ long_description = f.read().decode('utf-8') with open("%s/logdna/VERSION" % this_directory) as f: - version = f.read() + version = f.read().strip('\n') setup( name = 'logdna', packages = ['logdna'], + package_data={'': ['VERSION']}, version = version, description = 'A Python Package for Sending Logs to LogDNA', author = 'LogDNA Inc.', From 08e7c2f0d5be526b2b4d2ec4a9654a683894dd0d Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Thu, 12 Dec 2019 16:49:44 -0800 Subject: [PATCH 056/139] Bump version (#47) --- logdna/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logdna/VERSION b/logdna/VERSION index 4cda8f1..8af85be 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.5.2 +1.5.3 From 8edd10a7fe7e5c194015113de5664a0d3297ce66 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Tue, 21 Jan 2020 15:06:48 -0500 Subject: [PATCH 057/139] update circleci config for autodeployment (#48) --- .circleci/config.yml | 82 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f895176..63d25a3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,11 @@ -version: 2 +version: 2.1 +tagged_build_filters: &tagged_build_filters + branches: + ignore: /.*/ + tags: + only: /v[0-9]+\.[0-9]+\.[0-9]+/ jobs: - publish: + build: docker: - image: circleci/python:3 steps: @@ -8,27 +13,70 @@ jobs: - run: name: Check Tagged Push command: | - TAG_VERSION=$(git tag -l --sort=-v:refname | head -n 1 | cut -d'-' -f2) PKG_VERSION=$(cat logdna/VERSION) - if [[ "${TAG_VERSION}" != "${PKG_VERSION}" ]]; then + if [[ "${CIRCLE_TAG}" != "v${PKG_VERSION}" ]]; then echo "There is mismatch:" - echo " TAG_VERSION: ${TAG_VERSION}" - echo " PKG_VERSION: ${PKG_VERSION}" + echo " TAG_VERSION: ${CIRCLE_TAG}" + echo " PKG_VERSION: v${PKG_VERSION}" exit 1 fi + - run: python setup.py sdist + - persist_to_workspace: + root: . + paths: + - ./dist/logdna-*.tar.gz + release: + docker: + - image: circleci/golang:1.12 + steps: + - attach_workspace: + at: . + - run: go get -u github.com/tcnksm/ghr - run: - name: Publish + name: Create a Release command: | - sudo pip install twine - python setup.py sdist - twine upload dist/*.tar.gz + ghr \ + -n "LogDNA Python Logger ${CIRCLE_TAG}" \ + -t ${GITHUB_TOKEN} \ + -u ${CIRCLE_PROJECT_USERNAME} \ + -r ${CIRCLE_PROJECT_REPONAME} \ + -draft ${CIRCLE_TAG} ${CIRCLE_WORKING_DIRECTORY}/dist + - persist_to_workspace: + root: . + paths: + - ./dist/logdna-*.tar.gz + approve: + machine: true + steps: + - attach_workspace: + at: . + - persist_to_workspace: + root: . + paths: + - ./dist/logdna-*.tar.gz + publish: + docker: + - image: circleci/python:3 + steps: + - attach_workspace: + at: . + - run: sudo pip install twine + - run: twine upload dist/logdna-*.tar.gz workflows: - version: 2 - update_package: + update: jobs: + - build: + filters: *tagged_build_filters + - release: + requires: + - build + filters: *tagged_build_filters + - approve: + type: approval + requires: + - release + filters: *tagged_build_filters - publish: - filters: - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?/ - branches: - ignore: /.*/ + requires: + - approve + filters: *tagged_build_filters From babd6d40e657e5ed0ab48e17831828ccf7d3795f Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Tue, 21 Jan 2020 12:09:16 -0800 Subject: [PATCH 058/139] Add tests to cci file (#49) * update circleci config for autodeployment * add tests Co-authored-by: Samir Musali Co-authored-by: lvilya --- .circleci/config.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 63d25a3..3e287ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,18 @@ tagged_build_filters: &tagged_build_filters ignore: /.*/ tags: only: /v[0-9]+\.[0-9]+\.[0-9]+/ +test_build_filters: &test_build_filters + branches: + only: /.*/ + tags: + ignore: /v[0-9]+\.[0-9]+\.[0-9]+/ jobs: + test: + docker: + - image: circleci/python:3 + steps: + - checkout + - run: python logdnaHandlerTest.py build: docker: - image: circleci/python:3 @@ -65,7 +76,11 @@ jobs: workflows: update: jobs: + - test: + filters: *tagged_build_filters - build: + requires: + - test filters: *tagged_build_filters - release: requires: @@ -80,3 +95,7 @@ workflows: requires: - approve filters: *tagged_build_filters +test: + jobs: + - test: + filters: *test_build_filters \ No newline at end of file From 669064e5e5dd7c428ece4e5d13f7c38fbd097e17 Mon Sep 17 00:00:00 2001 From: vilyapilya Date: Tue, 21 Jan 2020 12:55:29 -0800 Subject: [PATCH 059/139] Test integration. (#51) * resolve conf * test if the chan ge will trigger the test * changes * correct * The failing test passed, now try the passing case * fix the checkout part * now try to fail again * fix back * remove the change * formating Co-authored-by: lvilya --- .circleci/config.yml | 4 ++-- logdnaHandlerTest.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e287ab..9bfe798 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,7 +77,7 @@ workflows: update: jobs: - test: - filters: *tagged_build_filters + filters: *test_build_filters - build: requires: - test @@ -98,4 +98,4 @@ workflows: test: jobs: - test: - filters: *test_build_filters \ No newline at end of file + filters: *test_build_filters diff --git a/logdnaHandlerTest.py b/logdnaHandlerTest.py index 3cc0c28..3c2903b 100644 --- a/logdnaHandlerTest.py +++ b/logdnaHandlerTest.py @@ -25,7 +25,6 @@ expectedLines = [] class successful_RequestHandler(BaseHTTPRequestHandler): def do_POST(self): - content_length = int(self.headers['Content-Length']) body = self.rfile.read(content_length) self.send_response(200) From 8483f94f8d8bcba87052fc0871504632b1e1587e Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Fri, 24 Jan 2020 14:42:19 -0500 Subject: [PATCH 060/139] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9bfe798..e7e5465 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,7 +51,7 @@ jobs: -t ${GITHUB_TOKEN} \ -u ${CIRCLE_PROJECT_USERNAME} \ -r ${CIRCLE_PROJECT_REPONAME} \ - -draft ${CIRCLE_TAG} ${CIRCLE_WORKING_DIRECTORY}/dist + -draft ${CIRCLE_TAG} ./dist/ - persist_to_workspace: root: . paths: From 84ade16b95a1221dffe5246a4979e510d3b4d370 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 1 Jun 2020 11:47:22 -0400 Subject: [PATCH 061/139] bump: SSL Certificate Expiration Patch (#54) --- logdna/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logdna/VERSION b/logdna/VERSION index 8af85be..94fe62c 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.5.3 +1.5.4 From 2717ef332af15f2c76e6688d1c3c4c3da0fc6497 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 1 Jun 2020 12:13:01 -0400 Subject: [PATCH 062/139] fix CircleCI Config (#55) --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e7e5465..95bcd29 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,12 +3,12 @@ tagged_build_filters: &tagged_build_filters branches: ignore: /.*/ tags: - only: /v[0-9]+\.[0-9]+\.[0-9]+/ + only: /[0-9]+\.[0-9]+\.[0-9]+/ test_build_filters: &test_build_filters branches: only: /.*/ tags: - ignore: /v[0-9]+\.[0-9]+\.[0-9]+/ + ignore: /[0-9]+\.[0-9]+\.[0-9]+/ jobs: test: docker: @@ -25,10 +25,10 @@ jobs: name: Check Tagged Push command: | PKG_VERSION=$(cat logdna/VERSION) - if [[ "${CIRCLE_TAG}" != "v${PKG_VERSION}" ]]; then + if [[ "${CIRCLE_TAG}" != "${PKG_VERSION}" ]]; then echo "There is mismatch:" echo " TAG_VERSION: ${CIRCLE_TAG}" - echo " PKG_VERSION: v${PKG_VERSION}" + echo " PKG_VERSION: ${PKG_VERSION}" exit 1 fi - run: python setup.py sdist @@ -47,7 +47,7 @@ jobs: name: Create a Release command: | ghr \ - -n "LogDNA Python Logger ${CIRCLE_TAG}" \ + -n "LogDNA Python Logger v${CIRCLE_TAG}" \ -t ${GITHUB_TOKEN} \ -u ${CIRCLE_PROJECT_USERNAME} \ -r ${CIRCLE_PROJECT_REPONAME} \ From 0a7763a2befbf598b59d7ab595b19e22b173fda5 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Tue, 2 Feb 2021 14:11:14 -0600 Subject: [PATCH 063/139] feat(lint): setup linting + test harness Updates package with poetry for local development lifecycle management. Adds scripts: - lint: execute lint rules w/o fixing `poetry run lint' - lint:fix: execute lint rules w/ fixing `poetry run lint:fix' - test: execute tests and generate coverage reports Adds nose as a test runner and coverage generator. Initially generating XUnit and html report. Minimum coverage level set to 67% (existing level) Lint config using flake8 for pep8 compliance Semver: minor --- .circleci/config.yml | 4 +- .gitignore | 3 + README.md | 130 +++++--- logdna/configs.py | 2 +- logdna/logdna.py | 69 ++-- logdna/utils.py | 10 +- logdnaHandlerTest.py | 153 --------- poetry.lock | 683 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 28 ++ scripts/lint.py | 20 ++ scripts/test.py | 25 ++ setup.cfg | 28 ++ setup.py | 36 +-- tests/__init__.py | 1 + tests/mock/__init.__.py | 0 tests/mock/log.py | 18 ++ tests/mock/server.py | 19 ++ tests/test_logger.py | 113 +++++++ tests/test_utils.py | 50 +++ 19 files changed, 1151 insertions(+), 241 deletions(-) delete mode 100644 logdnaHandlerTest.py create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 scripts/lint.py create mode 100644 scripts/test.py create mode 100644 tests/__init__.py create mode 100644 tests/mock/__init.__.py create mode 100644 tests/mock/log.py create mode 100644 tests/mock/server.py create mode 100644 tests/test_logger.py create mode 100644 tests/test_utils.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 95bcd29..391359e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,9 @@ jobs: - image: circleci/python:3 steps: - checkout - - run: python logdnaHandlerTest.py + - run: poetry install + - run: poetry run lint + - run: poetry run test build: docker: - image: circleci/python:3 diff --git a/.gitignore b/.gitignore index 527a94b..5d0eed0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ logdna.egg-info/ LogDNAHandler.log bin .installed.cfg +coverage/ +.coverage +Session.vim diff --git a/README.md b/README.md index 1a65cb2..9c7bf08 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,20 @@ --- -* **[Installation](#installation)** -* **[Setup](#setup)** -* **[Usage](#usage)** -* **[API](#api)** -* **[License](#license)** - +* [Installation](#installation) +* [Setup](#setup) +* [Usage](#usage) + * [Usage with File Config](#usage-with-file-config) +* [API](#api) + * [LogDNAHandler(key: string, [options: dict])](#logdnahandlerkey-string-options-dict) + * [key](#key) + * [options](#options) + * [log(line, [options])](#logline-options) + * [line](#line) + * [options](#options-1) +* [Development](#development) + * [Scripts](#scripts) +* [License](#license) ## Installation @@ -51,11 +59,11 @@ _**Required**_ * [LogDNA Ingestion Key](https://app.logdna.com/manage/profile) _**Optional**_ -* Hostname - *(String)* - max length 32 chars -* MAC Address - *(String)* -* IP Address - *(String)* -* Max Length - *(Boolean)* - formatted as options['max_length'] -* Index Meta - *(Boolean)* - formatted as options['index_meta'] +* Hostname - ([string][]) - max length 32 chars +* MAC Address - ([string][]) +* IP Address - ([string][]) +* Max Length - ([bool][]) - formatted as options['max_length'] +* Index Meta - ([bool][]) - formatted as options['index_meta'] ## Usage @@ -88,7 +96,7 @@ log.info('My Sample Log Line', opts) ### Usage with File Config -To use `LogDNAHandler` with [`fileConfig`](https://docs.python.org/2/library/logging.config.html#logging.config.fileConfig) (e.g., in a Django `settings.py` file): +To use [LogDNAHandler](#logdnahandlerkey-string-options-dict) with [fileConfig][] (e.g., in a Django `settings.py` file): ```python import os @@ -122,12 +130,12 @@ LOGGING = { ## API -### LogDNAHandler(key, [options]) ---- +### LogDNAHandler(key: [string][], [options: [dict][]]) + #### key * _**Required**_ -* Type: `String` +* Type: [string][] * Values: `YourAPIKey` The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your account. @@ -137,7 +145,7 @@ The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your ##### app * _Optional_ -* Type: `String` +* Type: [string][] * Default: `''` * Values: `YourCustomApp` * Max Length: `32` @@ -147,7 +155,7 @@ The default app passed along with every log sent through this instance. ##### env * _Optional_ -* Type: `String` +* Type: [string][] * Default: `''` * Values: `YourCustomEnv` * Max Length: `32` @@ -157,7 +165,7 @@ The default env passed along with every log sent through this instance. ##### hostname * _Optional_ -* Type: `String` +* Type: [string][] * Default: `''` * Values: `YourCustomHostname` * Max Length: `32` @@ -167,16 +175,18 @@ The default hostname passed along with every log sent through this instance. ##### include_standard_meta * _Optional_ -* Type: `Boolean` +* Type: [bool][] * Default: `False` -Python [`LogRecord` objects](https://docs.python.org/2/library/logging.html#logrecord-objects) includes language-specific information that may be useful metadata in logs. Setting `include_standard_meta` to `True` automatically populates meta objects with `name`, `pathname`, and `lineno` from the `LogRecord`. See [`LogRecord` docs](https://docs.python.org/2/library/logging.html#logrecord-objects) for more details about these values. +Python [LogRecord][] objects includes language-specific information that may be useful metadata in logs. +Setting `include_standard_meta` to `True` automatically populates meta objects with `name`, `pathname`, and `lineno` +from the [LogRecord][]. ##### index_meta * _Optional_ -* Type: `Boolean` +* Type: [bool][] * Default: `False` We allow meta objects to be passed with each line. By default these meta objects are stringified and not searchable, and are only displayed for informational purposes. @@ -189,7 +199,7 @@ If this option is set to True then meta objects are parsed and searchable up to ##### level * _Optional_ -* Type: `String` +* Type: [string][] * Default: `Info` * Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` * Max Length: `32` @@ -199,16 +209,16 @@ The default level passed along with every log sent through this instance. ##### verbose * _Optional_ -* Type: `String` or `Boolean` +* Type: [string][] or [bool][] * Default: `True` -* Values: False or any level +* Values: `False` or any level Sets the verbosity of log statements for failures. ##### request_timeout * _Optional_ -* Type: `int` +* Type: [int][] * Default: `30000` The amount of time (in ms) the request should wait for LogDNA to respond before timing out. @@ -216,7 +226,7 @@ The amount of time (in ms) the request should wait for LogDNA to respond before ##### tags * _Optional_ -* Type: `String[]` +* Type: [list][]<[string][]> * Default: `[]` List of tags used to dynamically group hosts. More information on tags is available at [How Do I Use Host Tags?](https://docs.logdna.com/docs/logdna-agent#section-how-do-i-use-host-tags-) @@ -224,17 +234,17 @@ List of tags used to dynamically group hosts. More information on tags is avail ##### url * _Optional_ -* Type: `String` +* Type: [string][] * Default: `'https://logs.logdna.com/logs/ingest'` A custom ingestion endpoint to stream log lines into. ### log(line, [options]) ---- + #### line * _Required_ -* Type: `String` +* Type: [string][] * Default: `''` * Max Length: `32000` @@ -245,7 +255,7 @@ The log line to be sent to LogDNA. ##### level * _Optional_ -* Type: `String` +* Type: [string][] * Default: `Info` * Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` * Max Length: `32` @@ -255,7 +265,7 @@ The level passed along with this log line. ##### app * _Optional_ -* Type: `String` +* Type: [string][] * Default: `''` * Values: `YourCustomApp` * Max Length: `32` @@ -265,7 +275,7 @@ The app passed along with this log line. ##### env * _Optional_ -* Type: `String` +* Type: [string][] * Default: `''` * Values: `YourCustomEnv` * Max Length: `32` @@ -275,16 +285,17 @@ The environment passed with this log line. ##### meta * _Optional_ -* Type: `JSON` +* Type: [dict][] * Default: `None` -A meta object for additional metadata about the log line that is passed. Please ensure values are JSON serializable, -values that are not JSON serializable will be removed and the respective keys will be added to the `__errors` string. +A standard dictonary containing additional metadata about the log line that is passed. Please ensure values are JSON serializable. + +**NOTE**: Values that are not JSON serializable will be removed and the respective keys will be added to the `__errors` string. ##### index_meta * _Optional_ -* Type: `Boolean` +* Type: [bool][] * Default: `False` We allow meta objects to be passed with each line. By default these meta objects will be stringified and will not be @@ -297,13 +308,58 @@ If this option is turned to true then meta objects will be parsed and will be se ##### timestamp * _Optional_ -* Default: `time.time()` +* Type: [float][] +* Default: [time.time()][] The time in seconds since the epoch to use for the log timestamp. It must be within one day or current time - if it is not, it is ignored and time.time() is used in its place. +## Development + +This project makes use of the [poetry][] package manager for local development. + +### Scripts + +**lint** +Run linting rules w/o attempting to fix them + +```shell +$ poetry run lint +``` + + +**lint:fix** + +Run lint rules against all local python files and attempt to fix where possible. + + +```shell +$ poetry run lint:fix +``` + +**test**: + +Runs all unit tests and generates coverage reports + +```shell +poetry run test +``` ## License MIT © [LogDNA](https://logdna.com/) +Copyright © 2017 [LogDNA][], released under an MIT license. See the [LICENSE](./LICENSE) file and [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) + *Happy Logging!* + +[bool]: https://docs.python.org/3/library/stdtypes.html#boolean-values +[dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[int]: https://docs.python.org/3/library/functions.html#int +[float]: https://docs.python.org/3/library/functions.html#float +[string]: https://docs.python.org/3/library/string.html +[list]: https://docs.python.org/3/library/stdtypes.html#list +[time.time()]: https://docs.python.org/3/library/time.html?#time.time +[poetry]: https://python-poetry.org +[LogDNA]: https://logdna.com/ +[LogRecord]: https://docs.python.org/2/library/logging.html#logrecord-objects +[fileConfig]: https://docs.python.org/2/library/logging.config.html#logging.config.fileConfig diff --git a/logdna/configs.py b/logdna/configs.py index 3ab9e55..3c808c9 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,7 +1,7 @@ from os import path with open("%s/VERSION" % path.abspath(path.dirname(__file__))) as f: - version = f.read().strip('\n') + version = f.read().strip('\n') defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, diff --git a/logdna/logdna.py b/logdna/logdna.py index b1a3132..eeb6000 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -7,11 +7,10 @@ import time from functools import reduce -from requests.adapters import HTTPAdapter -from urllib3.util import Retry from .configs import defaults from .utils import sanitize_meta, get_ip + class LogDNAHandler(logging.Handler): def __init__(self, key, options={}): logging.Handler.__init__(self) @@ -36,14 +35,20 @@ def __init__(self, key, options={}): self.app = options.get('app', '') self.env = options.get('env', '') self.url = options.get('url', defaults['LOGDNA_URL']) - self.request_timeout = options.get('request_timeout', defaults['DEFAULT_REQUEST_TIMEOUT']) - self.include_standard_meta = options.get('include_standard_meta', False) + self.request_timeout = options.get('request_timeout', + defaults['DEFAULT_REQUEST_TIMEOUT']) + self.include_standard_meta = options.get('include_standard_meta', + False) self.index_meta = options.get('index_meta', False) - self.flush_limit = options.get('flush_limit', defaults['FLUSH_BYTE_LIMIT']) - self.flush_interval_secs = options.get('flush_interval', defaults['FLUSH_INTERVAL_SECS']) - self.retry_interval_secs = options.get('retry_interval_secs', defaults['RETRY_INTERVAL_SECS']) + self.flush_limit = options.get('flush_limit', + defaults['FLUSH_BYTE_LIMIT']) + self.flush_interval_secs = options.get('flush_interval', + defaults['FLUSH_INTERVAL_SECS']) + self.retry_interval_secs = options.get('retry_interval_secs', + defaults['RETRY_INTERVAL_SECS']) self.tags = options.get('tags', []) - self.buf_retention_byte_limit = options.get('buf_retention_limit', defaults['BUF_RETENTION_BYTE_LIMIT']) + self.buf_retention_byte_limit = options.get( + 'buf_retention_limit', defaults['BUF_RETENTION_BYTE_LIMIT']) self.user_agent = options.get('user_agent', defaults['USER_AGENT']) if isinstance(self.tags, str): @@ -53,21 +58,28 @@ def __init__(self, key, options={}): self.setLevel(logging.DEBUG) self.lock = threading.RLock() + # TODO(esatterwhite): complexity too high (8) def buffer_log(self, message): if message and message['line']: if len(message['line']) > self.max_length: - message['line'] = message['line'][:self.max_length] + ' (cut off, too long...)' + message['line'] = message[ + 'line'][:self.max_length] + ' (cut off, too long...)' if self.verbose in ['true', 'debug', 'd']: - self.internalLogger.debug('Line was longer than %s chars and was truncated.', self.max_length) + self.internalLogger.debug( + 'Line was longer than %s chars and was truncated.', + self.max_length) - # Attempt to acquire lock to write to buf, otherwise write to secondary as flush occurs + # Attempt to acquire lock to write to buf + # otherwise write to secondary as flush occurs if self.lock.acquire(blocking=False): buf_size = reduce(lambda x, y: x + len(y['line']), self.buf, 0) if buf_size + len(message['line']) < self.buf_retention_byte_limit: self.buf.append(message) else: - self.internalLogger.debug('The buffer size exceeded the limit: %s', self.buf_retention_byte_limit) + self.internalLogger.debug( + 'The buffer size exceeded the limit: %s', + self.buf_retention_byte_limit) self.lock.release() if buf_size >= self.flush_limit and not self.exception_flag: @@ -76,7 +88,8 @@ def buffer_log(self, message): self.secondary.append(message) if not self.flusher: - interval = self.retry_interval_secs if self.exception_flag else self.flush_interval_secs + interval = (self.retry_interval_secs + if self.exception_flag else self.flush_interval_secs) self.flusher = threading.Timer(interval, self.flush) self.flusher.start() @@ -101,21 +114,20 @@ def send_request(self): self.secondary = [] data = {'e': 'ls', 'ls': self.buf} try: - res = requests.post( - url=self.url, - json=data, - auth=('user', self.key), - params={ - 'hostname': self.hostname, - 'ip': self.ip, - 'mac': self.mac if self.mac else None, - 'tags': self.tags if self.tags else None}, - stream=True, - timeout=self.request_timeout, - headers={'user-agent': self.user_agent} - ) + res = requests.post(url=self.url, + json=data, + auth=('user', self.key), + params={ + 'hostname': self.hostname, + 'ip': self.ip, + 'mac': self.mac if self.mac else None, + 'tags': self.tags if self.tags else None + }, + stream=True, + timeout=self.request_timeout, + headers={'user-agent': self.user_agent}) res.raise_for_status() - # when no RequestException happened + # when no RequestException happened self.clean_after_success() except requests.exceptions.RequestException as e: self.handle_exception(e) @@ -131,7 +143,8 @@ def flush(self): self.flusher = threading.Timer(1, self.flush) self.flusher.start() - def emit(self, record): + # TODO(esatterwhite): complexity too high (14) + def emit(self, record): # noqa: C901 msg = self.format(record) record = record.__dict__ opts = {} diff --git a/logdna/utils.py b/logdna/utils.py index d94846f..790ae0a 100644 --- a/logdna/utils.py +++ b/logdna/utils.py @@ -1,13 +1,15 @@ import json import socket + def is_jsonable(obj): try: json.dumps(obj) return True - except: + except (TypeError, OverflowError, ValueError): return False + def sanitize_meta(meta): keys_to_sanitize = [] for key, value in meta.items(): @@ -16,16 +18,18 @@ def sanitize_meta(meta): if keys_to_sanitize: for key in keys_to_sanitize: del meta[key] - meta['__errors'] = 'These keys have been sanitized: ' + ', '.join(keys_to_sanitize) + meta['__errors'] = 'These keys have been sanitized: ' + ', '.join( + keys_to_sanitize) return meta + def get_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # doesn't even have to be reachable s.connect(('10.255.255.255', 1)) ip = s.getsockname()[0] - except: + except Exception: ip = '127.0.0.1' finally: s.close() diff --git a/logdnaHandlerTest.py b/logdnaHandlerTest.py deleted file mode 100644 index 3c2903b..0000000 --- a/logdnaHandlerTest.py +++ /dev/null @@ -1,153 +0,0 @@ -import concurrent.futures -import json -import threading -import time - -from http.server import BaseHTTPRequestHandler,HTTPServer -import logging -import unittest - -from logdna import LogDNAHandler - -current_milli_time = lambda: int(round(time.time() * 1000)) - -key = '< YOUR INGESTION KEY HERE >' -log = logging.getLogger('logdna') -log.setLevel(logging.INFO) - -options = { - 'hostname': 'localhost', - 'url': 'http://localhost:8081', - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE' -} - -expectedLines = [] -class successful_RequestHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers['Content-Length']) - body = self.rfile.read(content_length) - self.send_response(200) - - self.end_headers() - body = json.loads(body)['ls'] - for keys in body: - expectedLines.append(keys['line']) - -class failed_RequestHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers['Content-Length']) - body = self.rfile.read(content_length) - self.send_response(400) - self.end_headers() - -class LogDNAHandlerTest(unittest.TestCase): - def server_recieves_messages(self): - options = { - 'hostname': 'localhost', - 'url': 'http://localhost:8081', - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE' - } - - server_address = ('localhost', 8081) - httpd = HTTPServer(server_address, successful_RequestHandler) - - test = LogDNAHandler(key, options) - log.addHandler(test) - line = "python python python" - - def send_log(): - log.info(line) - - server_thread = threading.Thread(target=httpd.handle_request) - logdna_thread = threading.Thread(target=send_log) - server_thread.daemon = True - logdna_thread.daemon = True - - server_thread.start() - logdna_thread.start() - - server_thread.join() - logdna_thread.join() - - self.assertEqual(len(expectedLines), 1) - self.assertIn(line, expectedLines) - - def messages_preserved_if_excp(self): - options = { - 'hostname': 'localhost', - 'url': 'http://localhost:8080', - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE' - } - server_address = ('localhost', 8080) - httpd = HTTPServer(server_address, failed_RequestHandler) - - failed_case_logger = LogDNAHandler(key, options) - log.addHandler(failed_case_logger) - line = "second test. server fails" - - def send_log_to_fail(): - log.info(line) - - server_thread = threading.Thread(target=httpd.handle_request) - logdna_thread = threading.Thread(target=send_log_to_fail) - server_thread.daemon = True - logdna_thread.daemon = True - - server_thread.start() - logdna_thread.start() - - server_thread.join() - logdna_thread.join() - self.assertEqual(len(failed_case_logger.buf), 1) - - - def stops_retention_when_buf_is_full(self): - options = { - 'hostname': 'localhost', - 'url': 'http://localhost:1337', - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE', - 'buf_retention_limit': 50, - 'equest_timeout': 10, - 'flush_interval': 1, - 'retry_interval_secs': 1 - } - server_address = ('localhost', 1337) - - httpd = HTTPServer(server_address, failed_RequestHandler) - - failed_case_logger = LogDNAHandler(key, options) - log.addHandler(failed_case_logger) - line = "when buffer grows bigger than we want" - lineTwo = "when buffer grows bigger than we want. And more and more" - - def send_log_to_fail(): - log.info(line) - log.info(lineTwo) - - - server_thread = threading.Thread(target=httpd.handle_request) - logdna_thread = threading.Thread(target=send_log_to_fail) - server_thread.daemon = True - logdna_thread.daemon = True - - server_thread.start() - logdna_thread.start() - - server_thread.join() - logdna_thread.join() - - self.assertEqual(len(failed_case_logger.buf), 1) - self.assertNotEqual(failed_case_logger.buf[0]['line'], lineTwo) - - def test_run_tests(self): - self.server_recieves_messages() - self.messages_preserved_if_excp() - self.stops_retention_when_buf_is_full() - - -if __name__ == '__main__': - unittest.main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..93c6849 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,683 @@ +[[package]] +name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "5.4" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] + +[[package]] +name = "decorator" +version = "4.4.2" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" + +[[package]] +name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "3.4.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "ipdb" +version = "0.13.4" +description = "IPython-enabled pdb" +category = "dev" +optional = false +python-versions = ">=2.7" + +[package.dependencies] +ipython = {version = ">=5.1.0", markers = "python_version >= \"3.4\""} + +[[package]] +name = "ipython" +version = "7.16.1" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.10" +pexpect = {version = "*", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "parso" +version = "0.8.1" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.3" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pygments" +version = "2.7.4" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "2.11.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tap.py" +version = "3.0" +description = "Test Anything Protocol (TAP) tools" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +yaml = ["more-itertools", "PyYAML (>=5.1)"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "traitlets" +version = "4.3.3" +description = "Traitlets Python config system" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +decorator = "*" +ipython-genutils = "*" +six = "*" + +[package.extras] +test = ["pytest", "mock"] + +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "yapf" +version = "0.30.0" +description = "A formatter for Python code." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.4.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "b357baa9d439bc7f7cf618918ae08e8da1f71844547172b4e448193c0f6bbb10" + +[metadata.files] +appnope = [ + {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, + {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"}, + {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"}, + {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"}, + {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"}, + {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"}, + {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"}, + {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"}, + {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"}, + {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"}, + {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"}, + {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"}, + {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"}, + {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"}, + {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"}, + {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"}, + {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"}, + {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"}, + {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"}, + {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"}, + {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"}, + {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"}, + {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"}, + {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"}, + {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"}, + {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"}, + {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"}, + {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"}, + {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"}, + {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"}, + {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"}, + {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"}, + {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"}, + {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"}, + {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"}, + {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"}, + {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"}, + {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"}, + {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"}, + {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"}, + {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"}, + {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"}, + {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"}, + {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"}, + {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"}, + {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, +] +flake8 = [ + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +importlib-metadata = [ + {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, + {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +ipdb = [ + {file = "ipdb-0.13.4.tar.gz", hash = "sha256:c85398b5fb82f82399fc38c44fe3532c0dde1754abee727d8f5cfcc74547b334"}, +] +ipython = [ + {file = "ipython-7.16.1-py3-none-any.whl", hash = "sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64"}, + {file = "ipython-7.16.1.tar.gz", hash = "sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +jedi = [ + {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, + {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +parso = [ + {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, + {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, + {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pygments = [ + {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"}, + {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, + {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, +] +pytest-cov = [ + {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, + {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +"tap.py" = [ + {file = "tap.py-3.0-py2.py3-none-any.whl", hash = "sha256:a598bfaa2e224d71f2e86147c2ef822c18ff2e1b8ef006397e5056b08f92f699"}, + {file = "tap.py-3.0.tar.gz", hash = "sha256:f5eeeeebfd64e53d32661752bb4c288589a3babbb96db3f391a4ec29f1359c70"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +traitlets = [ + {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, + {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] +urllib3 = [ + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +yapf = [ + {file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"}, + {file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"}, +] +zipp = [ + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1711ecb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[tool.poetry] +name = "logdna" +version = "1.5.4" +description = "A python package for sending logs" +authors = ["logdna "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.6" +requests = "^2.25.1" + +[tool.poetry.dev-dependencies] +coverage = "^5.4" +"tap.py" = "^3.0" +ipdb = "^0.13.4" +flake8 = "^3.8.4" +yapf = "^0.30.0" +pytest = "^6.2.2" +pytest-cov = "^2.11.1" + +[tool.poetry.scripts] +test = "scripts.test:run" +lint = "scripts.lint:run" +"lint:fix" = "scripts.lint:fix" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/scripts/lint.py b/scripts/lint.py new file mode 100644 index 0000000..87bc65f --- /dev/null +++ b/scripts/lint.py @@ -0,0 +1,20 @@ +from os import path +from subprocess import check_call + +ROOT = path.realpath(path.abspath(path.join(path.dirname(__file__), '..'))) + + +def run(): + check_call(['flake8', '--doctests']) + + +def fix(): + check_call(cwd=ROOT, + args=[ + 'yapf', '-r', '-i', + path.join(ROOT, 'logdna'), + path.join(ROOT, 'scripts'), + path.join(ROOT, 'tests') + ]) + + run() diff --git a/scripts/test.py b/scripts/test.py new file mode 100644 index 0000000..7c95bf4 --- /dev/null +++ b/scripts/test.py @@ -0,0 +1,25 @@ +import os +from os import path +from subprocess import check_call + +ROOT = path.realpath(path.abspath(path.join(path.dirname(__file__), '..'))) +COVERAGE_DIR = path.join(ROOT, 'coverage') +JUNIT_PATH = path.join(COVERAGE_DIR, 'test.xml') +print(JUNIT_PATH) + + +def run(): + try: + os.mkdir(COVERAGE_DIR) + except FileExistsError: + pass + + check_call([ + 'pytest', + '--junitxml={0}'.format(JUNIT_PATH), + '--cov=logdna', + '--cov-report=html', + '--cov-report=xml', + '--cov-config=setup.cfg', + '--cov-branch' + ]) diff --git a/setup.cfg b/setup.cfg index b88034e..92b7bc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,30 @@ [metadata] description-file = README.md + +[flake8] +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist +max-complexity = 10 + +[yapf] +based_on_style = pep8 +indent_width = 4 +use_tabs = False + +[tool:pytest] +minversion = 6.0 +testpaths = tests + +[coverage:run] +branch = True +source = logdna/* + +[coverage:html] +directory = coverage +show_contexts = True + +[coverage:xml] +output = coverage/coverage.xml + +[coverage:report] +fail_under = 67 + diff --git a/setup.py b/setup.py index cb3e933..58289e2 100644 --- a/setup.py +++ b/setup.py @@ -8,24 +8,24 @@ long_description = f.read().decode('utf-8') with open("%s/logdna/VERSION" % this_directory) as f: - version = f.read().strip('\n') + version = f.read().strip('\n') setup( - name = 'logdna', - packages = ['logdna'], - package_data={'': ['VERSION']}, - version = version, - description = 'A Python Package for Sending Logs to LogDNA', - author = 'LogDNA Inc.', - author_email = 'help@logdna.com', - license = 'MIT', - url = 'https://github.com/logdna/python', - download_url = ('https://github.com/logdna/python/tarball/%s' %(version)), - keywords = ['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], - install_requires=[ - 'requests', - ], - classifiers = [], - long_description=long_description, - long_description_content_type='text/markdown', + name='logdna', + packages=['logdna'], + package_data={'': ['VERSION']}, + version=version, + description='A Python Package for Sending Logs to LogDNA', + author='LogDNA Inc.', + author_email='help@logdna.com', + license='MIT', + url='https://github.com/logdna/python', + download_url=('https://github.com/logdna/python/tarball/%s' % (version)), + keywords=['logdna', 'logging', 'logs', 'python', 'logdna.com', 'logger'], + install_requires=[ + 'requests', + ], + classifiers=[], + long_description=long_description, + long_description_content_type='text/markdown', ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..cff2fe6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +from tap.tests.testcase import TestCase # NOQA diff --git a/tests/mock/__init.__.py b/tests/mock/__init.__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/mock/log.py b/tests/mock/log.py new file mode 100644 index 0000000..d9999e0 --- /dev/null +++ b/tests/mock/log.py @@ -0,0 +1,18 @@ +import logging +from threading import Thread + +__all__ = ['logger', 'info', 'LOGDNA_API_KEY'] +LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' +logger = logging.getLogger('logdna') +logger.setLevel(logging.INFO) + + +def info(*args): + def fn(): + for line in args: + logger.info(line) + + thread = Thread(target=fn) + thread.setDaemon(True) + thread.start() + return thread diff --git a/tests/mock/server.py b/tests/mock/server.py new file mode 100644 index 0000000..c0a1791 --- /dev/null +++ b/tests/mock/server.py @@ -0,0 +1,19 @@ +import socket +from http.server import HTTPServer +from threading import Thread + + +def get_port(): + s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) + s.bind(('localhost', 0)) + _, port = s.getsockname() + s.close() + return port + + +def start_server(port, cls): + server = HTTPServer(('localhost', port), cls) + thread = Thread(target=server.handle_request) + thread.setDaemon(True) + thread.start() + return thread diff --git a/tests/test_logger.py b/tests/test_logger.py new file mode 100644 index 0000000..3c5fa8d --- /dev/null +++ b/tests/test_logger.py @@ -0,0 +1,113 @@ +import json +import unittest + +from http.server import BaseHTTPRequestHandler +from logdna import LogDNAHandler +from .mock.server import get_port, start_server +from .mock.log import logger, info, LOGDNA_API_KEY + +expectedLines = [] + + +class SuccessfulRequestHandler(BaseHTTPRequestHandler): + def do_POST(self): + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + self.send_response(200) + + self.end_headers() + body = json.loads(body)['ls'] + for keys in body: + expectedLines.append(keys['line']) + + +class FailedRequestHandler(BaseHTTPRequestHandler): + def do_POST(self): + content_length = int(self.headers['Content-Length']) + self.rfile.read(content_length) + self.send_response(400) + self.end_headers() + + +class LogDNAHandlerTest(unittest.TestCase): + def server_recieves_messages(self): + port = get_port() + options = { + 'hostname': 'localhost', + 'url': 'http://localhost:{0}'.format(port), + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE' + } + + handler = LogDNAHandler(LOGDNA_API_KEY, options) + logger.addHandler(handler) + line = "python python python" + + server_thread = start_server(port, SuccessfulRequestHandler) + logdna_thread = info(line) + + server_thread.join() + logdna_thread.join() + + self.assertEqual(len(expectedLines), 1) + self.assertIn(line, expectedLines) + logger.removeHandler(handler) + + def messages_preserved_if_excp(self): + port = get_port() + options = { + 'hostname': 'localhost', + 'url': 'http://localhost:{0}'.format(port), + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE' + } + + handler = LogDNAHandler(LOGDNA_API_KEY, options) + logger.addHandler(handler) + line = "second test. server fails" + + server_thread = start_server(port, FailedRequestHandler) + logdna_thread = info(line) + + server_thread.join() + logdna_thread.join() + + self.assertEqual(len(handler.buf), 1) + logger.removeHandler(handler) + + def stops_retention_when_buf_is_full(self): + port = get_port() + options = { + 'hostname': 'localhost', + 'url': 'http://localhost:{0}'.format(port), + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE', + 'buf_retention_limit': 50, + 'equest_timeout': 10, + 'flush_interval': 1, + 'retry_interval_secs': 1 + } + + handler = LogDNAHandler(LOGDNA_API_KEY, options) + logger.addHandler(handler) + line = "when buffer grows bigger than we want" + lineTwo = "when buffer grows bigger than we want. And more and more" + + server_thread = start_server(port, FailedRequestHandler) + logdna_thread = info(line, lineTwo) + + server_thread.join() + logdna_thread.join() + + self.assertEqual(len(handler.buf), 1) + self.assertNotEqual(handler.buf[0]['line'], lineTwo) + logger.removeHandler(handler) + + def test_run_tests(self): + self.server_recieves_messages() + self.messages_preserved_if_excp() + self.stops_retention_when_buf_is_full() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..59fee87 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,50 @@ +import unittest +from unittest.mock import patch +from logdna.utils import is_jsonable, sanitize_meta, get_ip + +IP = '10.0.50.10' +VIP = '10.1.60.20' + + +class JSONTest(unittest.TestCase): + def setUp(self): + self.valid = {'key': 'value'} + self.invalid = {'key': set()} + + def test_serialize_valid_json(self): + self.assertTrue(is_jsonable(self.valid), 'json serializeble = True') + + def test_serialize_invalid_json(self): + self.assertFalse(is_jsonable(self.invalid), + 'non json serializable = False') + + +class SanitizeTest(unittest.TestCase): + def setUp(self): + self.valid = {'foo': 'bar', 'baz': 'whizbang'} + self.invalid = {'bar': 'foo', 'baz': set()} + + def test_sanitize_simple(self): + clean = sanitize_meta(self.valid) + self.assertDictEqual(clean, self.valid) + + def test_sanitize_complex(self): + clean = sanitize_meta(self.invalid) + self.assertDictEqual(clean, { + 'bar': 'foo', + '__errors': 'These keys have been sanitized: baz' + }) + + +class IPTest(unittest.TestCase): + @patch('socket.socket', **{'return_value.connect.side_effect': OSError()}) + def test_get_ip_socket_error(self, _): + self.assertEqual(get_ip(), '127.0.0.1', + 'default to localhost on error') + + @patch( + 'socket.socket', + **{'return_value.getsockname.return_value': [IP, VIP]} + ) + def test_get_ip_default(self, _): + self.assertEqual(get_ip(), IP, 'default to localhost on error') From 3b657e2ba8adc61408b22007ff01b634ebd64039 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Thu, 4 Feb 2021 13:36:18 -0600 Subject: [PATCH 064/139] doc: add contribution guides Includes a standard contributing and code of conduct document to eastablish a common ground of expectations from the community and from ourselves --- CODE_OF_CONDUCT.md | 138 +++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 37 ++++++++++++ LICENSE | 2 +- 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..0d5c4b2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,138 @@ +# Contributor's Code of Conduct + +If you contribute to this repo, you agree to abide by this code of conduct for +this community. + +We abide by the +[Contributor Covenant Code of Conduct, 2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). +It is reproduced below for ease of use. + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[opensource@logdna.com](mailto:opensource@logdna.com). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2032386 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +## Process + +We use a fork-and-PR process, also known as a triangular workflow. This process +is fairly common in open-source projects. Here's the basic workflow: + +1. Fork the upstream repo to create your own repo. This repo is called the origin repo. +2. Clone the origin repo to create a working directory on your local machine. +3. Work your changes on a branch in your working directory, then add, commit, and push your work to your origin repo. +4. Submit your changes as a PR against the upstream repo. You can use the upstream repo UI to do this. +5. Maintainers review your changes. If they ask for changes, you work on your + origin repo's branch and then submit another PR. Otherwise, if no changes are made, + then the branch with your PR is merged to upstream's main trunk, the master branch. + +When you work in a triangular workflow, you have the upstream repo, the origin +repo, and then your working directory (the clone of the origin repo). You do +a `git fetch` from upstream to local, push from local to origin, and then do a PR from origin to +upstream—a triangle. + +If this workflow is too much to understand to start, that's ok! You can use +GitHub's UI to make a change, which is autoset to do most of this process for +you. We just want you to be aware of how the entire process works before +proposing a change. + +Thank you for your contributions; we appreciate you! + +## License + +Note that we use a standard [MIT](./LICENSE) license on this repo. + +## Coding style + +Currently the project is auto formatted following the [PEP8][] +style guide + +[PEP8]: https://www.python.org/dev/peps/pep-0008 diff --git a/LICENSE b/LICENSE index 37e8e3d..8c79b7d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 LogDNA +Copyright (c) 2017 LogDNA Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c0f04dab09f5b6d1c2ec24e00ee7eca34c9aa28c Mon Sep 17 00:00:00 2001 From: markcurtis1970 Date: Mon, 7 Sep 2020 18:32:03 +0100 Subject: [PATCH 065/139] Update with example of using env var for key --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c7bf08..79623cb 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,12 @@ $ pip install logdna ```python import logging from logdna import LogDNAHandler +import os -key = 'YOUR INGESTION KEY HERE' +# Set your key as an env variable +# then import here, its best not to +# hard code your key! +key=os.environ['INGESTION_KEY'] log = logging.getLogger('logdna') log.setLevel(logging.INFO) From 85a543c9dc27a3c6064e790be0ba1c475187c38d Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:00:14 -0600 Subject: [PATCH 066/139] docs: add @respectus as a contributor --- .all-contributorsrc | 26 ++++++++++++++++++++++++++ README.md | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .all-contributorsrc diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..8b4ac31 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,26 @@ +{ + "projectName": "python", + "projectOwner": "logdna", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": true, + "commitConvention": "angular", + "contributors": [ + { + "login": "respectus", + "name": "Muaz Siddiqui", + "avatar_url": "https://avatars.githubusercontent.com/u/1046364?v=4", + "profile": "https://github.com/respectus", + "contributions": [ + "code", + "doc", + "test" + ] + } + ], + "contributorsPerLine": 7 +} diff --git a/README.md b/README.md index 79623cb..00fcec6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@

+ +[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) + @@ -348,6 +351,27 @@ Runs all unit tests and generates coverage reports ```shell poetry run test ``` + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + +

Muaz Siddiqui

💻 📖 ⚠️
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + ## License MIT © [LogDNA](https://logdna.com/) From 4d5022f93948cca239ebc104e34034c350956f65 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:01:24 -0600 Subject: [PATCH 067/139] docs: add @smusali as a contributor --- .all-contributorsrc | 11 +++++++++++ README.md | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8b4ac31..db5393f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -20,6 +20,17 @@ "doc", "test" ] + }, + { + "login": "smusali", + "name": "Samir Musali", + "avatar_url": "https://avatars.githubusercontent.com/u/34287490?v=4", + "profile": "https://github.com/smusali", + "contributions": [ + "code", + "doc", + "test" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 00fcec6..f581468 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) @@ -362,6 +362,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d +

Muaz Siddiqui

💻 📖 ⚠️

Samir Musali

💻 📖 ⚠️
From e3191f577fb7ddf7590ca1e1bf239f66d2f30fd0 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:03:11 -0600 Subject: [PATCH 068/139] docs: add @vilyapilya as a contributor --- .all-contributorsrc | 11 +++++++++++ README.md | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index db5393f..e1d2e02 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -31,6 +31,17 @@ "doc", "test" ] + }, + { + "login": "vilyapilya", + "name": "vilyapilya", + "avatar_url": "https://avatars.githubusercontent.com/u/17367511?v=4", + "profile": "https://github.com/vilyapilya", + "contributions": [ + "code", + "maintenance", + "test" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index f581468..8606fd9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) @@ -363,6 +363,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Muaz Siddiqui

💻 📖 ⚠️
Samir Musali

💻 📖 ⚠️ +
vilyapilya

💻 🚧 ⚠️ From e7aa7cb2624313c065f7bbc8a129c1a4841f9ec2 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:03:27 -0600 Subject: [PATCH 069/139] docs: add @mikehu as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index e1d2e02..b82f124 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -42,6 +42,15 @@ "maintenance", "test" ] + }, + { + "login": "mikehu", + "name": "Mike Hu", + "avatar_url": "https://avatars.githubusercontent.com/u/981800?v=4", + "profile": "https://github.com/mikehu", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 8606fd9..7e7cf6e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-) @@ -364,6 +364,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Muaz Siddiqui

💻 📖 ⚠️
Samir Musali

💻 📖 ⚠️
vilyapilya

💻 🚧 ⚠️ +
Mike Hu

📖 From 0a334bdb5690049634c64eb0f9c6c1026b9ab001 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:04:03 -0600 Subject: [PATCH 070/139] docs: add @esatterwhite as a contributor --- .all-contributorsrc | 12 ++++++++++++ README.md | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index b82f124..273db05 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -51,6 +51,18 @@ "contributions": [ "doc" ] + }, + { + "login": "esatterwhite", + "name": "Eric Satterwhite", + "avatar_url": "https://avatars.githubusercontent.com/u/148561?v=4", + "profile": "http://codedependant.net/", + "contributions": [ + "code", + "doc", + "test", + "tool" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 7e7cf6e..f16f717 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) @@ -365,6 +365,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Samir Musali

💻 📖 ⚠️
vilyapilya

💻 🚧 ⚠️
Mike Hu

📖 +
Eric Satterwhite

💻 📖 ⚠️ 🔧 From ea495b479cc0549ff68a1e816c3f7a174c1c138c Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:04:57 -0600 Subject: [PATCH 071/139] docs: add @utek as a contributor --- .all-contributorsrc | 10 ++++++++++ README.md | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 273db05..2b889fd 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -63,6 +63,16 @@ "test", "tool" ] + }, + { + "login": "utek", + "name": "Łukasz Bołdys (Lukasz Boldys)", + "avatar_url": "https://avatars.githubusercontent.com/u/128036?v=4", + "profile": "http://dev.utek.pl/", + "contributions": [ + "code", + "bug" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index f16f717..d0c6833 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) @@ -366,6 +366,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
vilyapilya

💻 🚧 ⚠️
Mike Hu

📖
Eric Satterwhite

💻 📖 ⚠️ 🔧 +
Łukasz Bołdys (Lukasz Boldys)

💻 🐛 From 8971bd713d74cd9386c60a50cc75a7fc3591f544 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:05:26 -0600 Subject: [PATCH 072/139] docs: add @baronomasia as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2b889fd..7cf5ee9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -73,6 +73,15 @@ "code", "bug" ] + }, + { + "login": "baronomasia", + "name": "Ryan", + "avatar_url": "https://avatars.githubusercontent.com/u/4133158?v=4", + "profile": "https://github.com/baronomasia", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index d0c6833..023ccb7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-) @@ -367,6 +367,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Mike Hu

📖
Eric Satterwhite

💻 📖 ⚠️ 🔧
Łukasz Bołdys (Lukasz Boldys)

💻 🐛 +
Ryan

📖 From 3cc9e232d0b12b9f0315efb1d618426b486a2604 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:06:06 -0600 Subject: [PATCH 073/139] docs: add @LYHuang as a contributor --- .all-contributorsrc | 10 ++++++++++ README.md | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7cf5ee9..1b2962b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -82,6 +82,16 @@ "contributions": [ "doc" ] + }, + { + "login": "LYHuang", + "name": "Mike Huang", + "avatar_url": "https://avatars.githubusercontent.com/u/14082239?v=4", + "profile": "https://github.com/LYHuang", + "contributions": [ + "code", + "bug" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 023ccb7..bbc2c72 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-) @@ -369,6 +369,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Łukasz Bołdys (Lukasz Boldys)

💻 🐛
Ryan

📖 + +
Mike Huang

💻 🐛 + From 23bbab48cdef4aaef6813459dbdb23dbd9c60374 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:07:04 -0600 Subject: [PATCH 074/139] docs: add @danmaas as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1b2962b..70b2c70 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -92,6 +92,15 @@ "code", "bug" ] + }, + { + "login": "danmaas", + "name": "Dan Maas", + "avatar_url": "https://avatars.githubusercontent.com/u/9013104?v=4", + "profile": "https://www.medium.com/@dmaas", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index bbc2c72..e80f5a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-) @@ -371,6 +371,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Mike Huang

💻 🐛 +
Dan Maas

💻 From 42e9c6bddc2bce29714072bba37d85d9a734eac2 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:07:24 -0600 Subject: [PATCH 075/139] docs: add @dchai76 as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 70b2c70..3c42ebe 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -101,6 +101,15 @@ "contributions": [ "code" ] + }, + { + "login": "dchai76", + "name": "DChai", + "avatar_url": "https://avatars.githubusercontent.com/u/13873467?v=4", + "profile": "https://github.com/dchai76", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index e80f5a5..d966b01 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) @@ -372,6 +372,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Mike Huang

💻 🐛
Dan Maas

💻 +
DChai

📖 From 69e8f7cc0782a778d0da230ff1a8feb5f8cee352 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:07:55 -0600 Subject: [PATCH 076/139] docs: add @jdemaeyer as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3c42ebe..2b8e841 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -110,6 +110,15 @@ "contributions": [ "doc" ] + }, + { + "login": "jdemaeyer", + "name": "Jakob de Maeyer ", + "avatar_url": "https://avatars.githubusercontent.com/u/10531844?v=4", + "profile": "https://naboa.de/", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index d966b01..b67edb2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-) @@ -373,6 +373,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Mike Huang

💻 🐛
Dan Maas

💻
DChai

📖 +
Jakob de Maeyer

💻 From 04081f039d6136a66b259f7245ad7845ef1c080a Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:08:30 -0600 Subject: [PATCH 077/139] docs: add @sataloger as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2b8e841..2be699d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -119,6 +119,15 @@ "contributions": [ "code" ] + }, + { + "login": "sataloger", + "name": "Andrey Babak", + "avatar_url": "https://avatars.githubusercontent.com/u/359111?v=4", + "profile": "https://github.com/sataloger", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index b67edb2..79e749a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-) @@ -374,6 +374,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Dan Maas

💻
DChai

📖
Jakob de Maeyer

💻 +
Andrey Babak

💻 From 7b9f04b86a1e813bccea29ea8db1554808ea48b6 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:09:00 -0600 Subject: [PATCH 078/139] docs: add @SpainTrain as a contributor --- .all-contributorsrc | 10 ++++++++++ README.md | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2be699d..0e37c7b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -128,6 +128,16 @@ "contributions": [ "code" ] + }, + { + "login": "SpainTrain", + "name": "Mike S", + "avatar_url": "https://avatars.githubusercontent.com/u/380032?v=4", + "profile": "https://github.com/mike-spainhower", + "contributions": [ + "code", + "doc" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 79e749a..77a847c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-) @@ -375,6 +375,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
DChai

📖
Jakob de Maeyer

💻
Andrey Babak

💻 +
Mike S

💻 📖 From c1e69bfc965e1e1e5717ec7af3271dc0bf6b502d Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:10:20 -0600 Subject: [PATCH 079/139] docs: add @btashton as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 0e37c7b..633843b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -138,6 +138,15 @@ "code", "doc" ] + }, + { + "login": "btashton", + "name": "Brennan Ashton", + "avatar_url": "https://avatars.githubusercontent.com/u/173245?v=4", + "profile": "https://github.com/btashton", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 77a847c..a86aec2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors-) @@ -376,6 +376,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Jakob de Maeyer

💻
Andrey Babak

💻
Mike S

💻 📖 +
Brennan Ashton

💻 From 7ba6992287f98c478da72f5982a0869b5d835df7 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:10:49 -0600 Subject: [PATCH 080/139] docs: add @inkrement as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 633843b..aa5fa5a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -147,6 +147,15 @@ "contributions": [ "code" ] + }, + { + "login": "inkrement", + "name": "Christian Hotz-Behofsits", + "avatar_url": "https://avatars.githubusercontent.com/u/604528?v=4", + "profile": "http://justrocketscience.com/", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index a86aec2..c65e7d9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) @@ -378,6 +378,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Mike S

💻 📖
Brennan Ashton

💻 + +
Christian Hotz-Behofsits

💻 + From 6a2cedef6695e18a981a3605176c9cffdd159827 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:14:45 -0600 Subject: [PATCH 081/139] docs: update @inkrement as a contributor --- .all-contributorsrc | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index aa5fa5a..13dc051 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -154,7 +154,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/604528?v=4", "profile": "http://justrocketscience.com/", "contributions": [ - "code" + "code", + "bug" ] } ], diff --git a/README.md b/README.md index c65e7d9..308cd34 100644 --- a/README.md +++ b/README.md @@ -379,7 +379,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Brennan Ashton

💻 -
Christian Hotz-Behofsits

💻 +
Christian Hotz-Behofsits

💻 🐛 From 14760857649b56207240bae907386a33f7f1666b Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:16:17 -0600 Subject: [PATCH 082/139] docs: add @kurtiss as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 13dc051..8b58687 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -157,6 +157,15 @@ "code", "bug" ] + }, + { + "login": "kurtiss", + "name": "Kurtiss Hare", + "avatar_url": "https://avatars.githubusercontent.com/u/108118?v=4", + "profile": "http://www.kinoarts.com/blog/", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 308cd34..95d8321 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-) @@ -380,6 +380,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Christian Hotz-Behofsits

💻 🐛 +
Kurtiss Hare

🐛 From 9ab0e3932180c41bff1cd0944c24fb8e208f4391 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:17:22 -0600 Subject: [PATCH 083/139] docs: add @rudyryk as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8b58687..9298253 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -166,6 +166,15 @@ "contributions": [ "bug" ] + }, + { + "login": "rudyryk", + "name": "Alexey Kinev", + "avatar_url": "https://avatars.githubusercontent.com/u/4500?v=4", + "profile": "https://twitter.com/rudyryk", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 95d8321..1d88253 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-) @@ -381,6 +381,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Christian Hotz-Behofsits

💻 🐛
Kurtiss Hare

🐛 +
Alexey Kinev

🐛 From 3727bc3386d3dd6465e0b0d15675a091a3743c24 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 09:18:51 -0600 Subject: [PATCH 084/139] docs: add @matthiasfru as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 9298253..db3c7ec 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -175,6 +175,15 @@ "contributions": [ "bug" ] + }, + { + "login": "matthiasfru", + "name": "matthiasfru", + "avatar_url": "https://avatars.githubusercontent.com/u/24245643?v=4", + "profile": "https://github.com/matthiasfru", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index 1d88253..431d348 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-) @@ -382,6 +382,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Christian Hotz-Behofsits

💻 🐛
Kurtiss Hare

🐛
Alexey Kinev

🐛 +
matthiasfru

🐛 From 9032d173e98fb3d4edeb5a3cfad1fcfd31438107 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 11:16:24 -0600 Subject: [PATCH 085/139] chore(deps): taskypi@1.6.0 add taskypi as a general task runner. The `scripts` in the pyproject.toml file are added as runnable script to the user path when they install the package. This is not the desirable outcome and can lead to confusing behviors. This allows up to implement develomnet tasks similar to npm script with out effecting the installable package or host environment --- poetry.lock | 72 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 93c6849..6aceb28 100644 --- a/poetry.lock +++ b/poetry.lock @@ -198,6 +198,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "mslex" +version = "0.3.0" +description = "shlex for windows" +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "packaging" version = "20.9" @@ -265,6 +273,17 @@ python-versions = ">=3.6" [package.dependencies] wcwidth = "*" +[[package]] +name = "psutil" +version = "5.8.0" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -387,6 +406,19 @@ python-versions = "*" [package.extras] yaml = ["more-itertools", "PyYAML (>=5.1)"] +[[package]] +name = "taskipy" +version = "1.6.0" +description = "tasks runner for python projects" +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +mslex = ">=0.3.0,<0.4.0" +psutil = ">=5.7.2,<6.0.0" +toml = ">=0.10.0,<0.11.0" + [[package]] name = "toml" version = "0.10.2" @@ -463,7 +495,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "b357baa9d439bc7f7cf618918ae08e8da1f71844547172b4e448193c0f6bbb10" +content-hash = "f860b4efd8c9effba27c1d04219d50794d74208204b7e3efca8ada9686fafea9" [metadata.files] appnope = [ @@ -584,6 +616,10 @@ mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +mslex = [ + {file = "mslex-0.3.0-py2.py3-none-any.whl", hash = "sha256:380cb14abf8fabf40e56df5c8b21a6d533dc5cbdcfe42406bbf08dda8f42e42a"}, + {file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"}, +] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, @@ -608,6 +644,36 @@ prompt-toolkit = [ {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, ] +psutil = [ + {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, + {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"}, + {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"}, + {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"}, + {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"}, + {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"}, + {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"}, + {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"}, + {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"}, + {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"}, + {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"}, + {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"}, + {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"}, + {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"}, + {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"}, + {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"}, + {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"}, + {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"}, + {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"}, + {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"}, + {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"}, + {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"}, + {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"}, + {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"}, + {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"}, + {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"}, + {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"}, + {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"}, +] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -652,6 +718,10 @@ six = [ {file = "tap.py-3.0-py2.py3-none-any.whl", hash = "sha256:a598bfaa2e224d71f2e86147c2ef822c18ff2e1b8ef006397e5056b08f92f699"}, {file = "tap.py-3.0.tar.gz", hash = "sha256:f5eeeeebfd64e53d32661752bb4c288589a3babbb96db3f391a4ec29f1359c70"}, ] +taskipy = [ + {file = "taskipy-1.6.0-py3-none-any.whl", hash = "sha256:33ee1d52b378cb4af3678fc459b75c3028f594c5e8e42ac0696cbe3e95d47394"}, + {file = "taskipy-1.6.0.tar.gz", hash = "sha256:ec4d1f2208ae24218950e3a2812e4e8b4397b1f65a6ad7e2b1240b702042fa3e"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, diff --git a/pyproject.toml b/pyproject.toml index 1711ecb..15df8c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ flake8 = "^3.8.4" yapf = "^0.30.0" pytest = "^6.2.2" pytest-cov = "^2.11.1" +taskipy = "^1.6.0" [tool.poetry.scripts] test = "scripts.test:run" From f5a8b182941a0c514594cbaa7a3ac5952173d5a8 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 12:11:04 -0600 Subject: [PATCH 086/139] feat(tasks): convert run scripts to tasks This changes the poetry run script to use the single task runner. This will prevent the scripts from being included and installed with the package --- README.md | 10 +++++--- pyproject.toml | 32 ++++++++++++++++++++++---- scripts/json_coverage.py | 49 ++++++++++++++++++++++++++++++++++++++++ scripts/lint.py | 20 ---------------- scripts/test.py | 25 -------------------- setup.cfg | 21 ----------------- setup.py | 9 +++++++- 7 files changed, 91 insertions(+), 75 deletions(-) create mode 100644 scripts/json_coverage.py delete mode 100644 scripts/lint.py delete mode 100644 scripts/test.py diff --git a/README.md b/README.md index 431d348..817e7a9 100644 --- a/README.md +++ b/README.md @@ -325,13 +325,17 @@ The time in seconds since the epoch to use for the log timestamp. It must be wit This project makes use of the [poetry][] package manager for local development. +```shell +$ poetry isntall +``` + ### Scripts **lint** Run linting rules w/o attempting to fix them ```shell -$ poetry run lint +$ poetry run task lint ``` @@ -341,7 +345,7 @@ Run lint rules against all local python files and attempt to fix where possible. ```shell -$ poetry run lint:fix +$ poetry run task lint:fix ``` **test**: @@ -349,7 +353,7 @@ $ poetry run lint:fix Runs all unit tests and generates coverage reports ```shell -poetry run test +poetry run task test ``` ## Contributors ✨ diff --git a/pyproject.toml b/pyproject.toml index 15df8c8..10619cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "logdna" version = "1.5.4" -description = "A python package for sending logs" +description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" @@ -19,11 +19,33 @@ pytest = "^6.2.2" pytest-cov = "^2.11.1" taskipy = "^1.6.0" -[tool.poetry.scripts] -test = "scripts.test:run" -lint = "scripts.lint:run" -"lint:fix" = "scripts.lint:fix" +[tool.taskipy.tasks] +pre_test = "mkdir coverage -p" +test = "pytest --junitxml=coverage/test.xml --cov=logdna --cov-report=html" +post_test = "python scripts/json_coverage.py" +lint = "flake8 --doctests" +"lint:fix" = "yapf -r -i logdna scripts tests" +"post_lint:fix" = "task lint" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" + +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = "tests" + +[tool.coverage.run] +branch = true +source = ["logdna"] + +[tool.coverage.report] +fail_under = 67 +show_missing = true + +[tool.coverage.json] +output = "coverage/coverage.json" + +[tool.coverage.html] +directory = "coverage" +show_contexts = true diff --git a/scripts/json_coverage.py b/scripts/json_coverage.py new file mode 100644 index 0000000..c78174e --- /dev/null +++ b/scripts/json_coverage.py @@ -0,0 +1,49 @@ +import json +from os import path +from coverage import Coverage + +ROOT = path.realpath(path.abspath(path.join(path.dirname(__file__), '..'))) +COVERAGE_DIR = path.join(ROOT, 'coverage') +JUNIT_PATH = path.join(COVERAGE_DIR, 'test.xml') + + +def json_coverage(): + COVERAGE_FILE = path.join(COVERAGE_DIR, 'coverage-final.json') + COVERAGE_SUMMARY = path.join(COVERAGE_DIR, 'coverage-summary.json') + + coverage = Coverage(config_file=path.join(ROOT, 'pyproject.toml')) + coverage.load() + coverage.json_report(outfile=COVERAGE_FILE) + + report = json.load(open(COVERAGE_FILE)) + totals = report.get('totals') + summary = { + 'lines': { + 'total': totals['covered_lines'] + totals['missing_lines'], + 'covered': totals['covered_lines'], + 'pct': totals['covered_lines'] / ( + totals['covered_lines'] + totals['missing_lines'] + ) * 100 + }, + 'statements': { + 'total': None, + 'covered': None, + 'pct': None, + }, + 'functions': { + 'total': None, + 'covered': None, + 'pct': None, + }, + 'branches': { + 'total': totals['num_branches'], + 'covered': totals['covered_branches'], + 'pct': totals['covered_branches'] / totals['num_branches'] * 100 + } + } + + json.dump({'total': summary}, open(COVERAGE_SUMMARY, 'w')) + + +if __name__ == "__main__": + json_coverage() diff --git a/scripts/lint.py b/scripts/lint.py deleted file mode 100644 index 87bc65f..0000000 --- a/scripts/lint.py +++ /dev/null @@ -1,20 +0,0 @@ -from os import path -from subprocess import check_call - -ROOT = path.realpath(path.abspath(path.join(path.dirname(__file__), '..'))) - - -def run(): - check_call(['flake8', '--doctests']) - - -def fix(): - check_call(cwd=ROOT, - args=[ - 'yapf', '-r', '-i', - path.join(ROOT, 'logdna'), - path.join(ROOT, 'scripts'), - path.join(ROOT, 'tests') - ]) - - run() diff --git a/scripts/test.py b/scripts/test.py deleted file mode 100644 index 7c95bf4..0000000 --- a/scripts/test.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -from os import path -from subprocess import check_call - -ROOT = path.realpath(path.abspath(path.join(path.dirname(__file__), '..'))) -COVERAGE_DIR = path.join(ROOT, 'coverage') -JUNIT_PATH = path.join(COVERAGE_DIR, 'test.xml') -print(JUNIT_PATH) - - -def run(): - try: - os.mkdir(COVERAGE_DIR) - except FileExistsError: - pass - - check_call([ - 'pytest', - '--junitxml={0}'.format(JUNIT_PATH), - '--cov=logdna', - '--cov-report=html', - '--cov-report=xml', - '--cov-config=setup.cfg', - '--cov-branch' - ]) diff --git a/setup.cfg b/setup.cfg index 92b7bc5..a8d4c5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[metadata] -description-file = README.md - [flake8] exclude = .git,__pycache__,docs/source/conf.py,old,build,dist max-complexity = 10 @@ -10,21 +7,3 @@ based_on_style = pep8 indent_width = 4 use_tabs = False -[tool:pytest] -minversion = 6.0 -testpaths = tests - -[coverage:run] -branch = True -source = logdna/* - -[coverage:html] -directory = coverage -show_contexts = True - -[coverage:xml] -output = coverage/coverage.xml - -[coverage:report] -fail_under = 67 - diff --git a/setup.py b/setup.py index 58289e2..ffafd56 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,14 @@ install_requires=[ 'requests', ], - classifiers=[], + classifiers=[ + 'Topic :: System :: Logging', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9' + ], long_description=long_description, long_description_content_type='text/markdown', ) From 1c5be37ef32776f2d0ba2b68b67cebb58b1f0177 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 17 Feb 2021 13:03:08 -0600 Subject: [PATCH 087/139] feat(ci): include jenkins setup adds a jenkins file to run tests + build on tags. This resembles the circle ci setup with the exception of the github release setup. This will be re-implemented with semantic-release --- .circleci/config.yml | 103 ------------------------------------------- .gitignore | 3 +- Jenkinsfile | 78 ++++++++++++++++++++++++++++++++ README.md | 9 ++-- setup.cfg | 3 ++ 5 files changed, 88 insertions(+), 108 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 Jenkinsfile diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 391359e..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,103 +0,0 @@ -version: 2.1 -tagged_build_filters: &tagged_build_filters - branches: - ignore: /.*/ - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+/ -test_build_filters: &test_build_filters - branches: - only: /.*/ - tags: - ignore: /[0-9]+\.[0-9]+\.[0-9]+/ -jobs: - test: - docker: - - image: circleci/python:3 - steps: - - checkout - - run: poetry install - - run: poetry run lint - - run: poetry run test - build: - docker: - - image: circleci/python:3 - steps: - - checkout - - run: - name: Check Tagged Push - command: | - PKG_VERSION=$(cat logdna/VERSION) - if [[ "${CIRCLE_TAG}" != "${PKG_VERSION}" ]]; then - echo "There is mismatch:" - echo " TAG_VERSION: ${CIRCLE_TAG}" - echo " PKG_VERSION: ${PKG_VERSION}" - exit 1 - fi - - run: python setup.py sdist - - persist_to_workspace: - root: . - paths: - - ./dist/logdna-*.tar.gz - release: - docker: - - image: circleci/golang:1.12 - steps: - - attach_workspace: - at: . - - run: go get -u github.com/tcnksm/ghr - - run: - name: Create a Release - command: | - ghr \ - -n "LogDNA Python Logger v${CIRCLE_TAG}" \ - -t ${GITHUB_TOKEN} \ - -u ${CIRCLE_PROJECT_USERNAME} \ - -r ${CIRCLE_PROJECT_REPONAME} \ - -draft ${CIRCLE_TAG} ./dist/ - - persist_to_workspace: - root: . - paths: - - ./dist/logdna-*.tar.gz - approve: - machine: true - steps: - - attach_workspace: - at: . - - persist_to_workspace: - root: . - paths: - - ./dist/logdna-*.tar.gz - publish: - docker: - - image: circleci/python:3 - steps: - - attach_workspace: - at: . - - run: sudo pip install twine - - run: twine upload dist/logdna-*.tar.gz -workflows: - update: - jobs: - - test: - filters: *test_build_filters - - build: - requires: - - test - filters: *tagged_build_filters - - release: - requires: - - build - filters: *tagged_build_filters - - approve: - type: approval - requires: - - release - filters: *tagged_build_filters - - publish: - requires: - - approve - filters: *tagged_build_filters -test: - jobs: - - test: - filters: *test_build_filters diff --git a/.gitignore b/.gitignore index 5d0eed0..fe9e6fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -**/*.pyc +*.pyc **/*cache* build/ develop-eggs @@ -11,3 +11,4 @@ bin coverage/ .coverage Session.vim +.cache diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..195c815 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,78 @@ +library 'magic-butler-catalogue' +def PROJECT_NAME = 'logdna-python' +def TRIGGER_PATTERN = ".*@logdnabot.*" + +pipeline { + agent none + + options { + timestamps() + ansiColor 'xterm' + } + + triggers { + issueCommentTrigger(TRIGGER_PATTERN) + } + + stages { + stage('Test') { + agent { + docker { + image "us.gcr.io/logdna-k8s/python:3.7-ci" + customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" + } + } + + environment { + XDG_CONFIG_HOME = pwd() + POETRY_VIRTUALENV_IN_PROJECT = true + } + + steps { + sh 'poetry config --list --local' + sh 'poetry install --no-interaction -vvv' + sh 'poetry run task lint' + sh 'poetry run task test' + } + + post { + always { + junit 'coverage/test.xml' + publishHTML target: [ + allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'coverage', + reportFiles: 'index.html', + reportName: "coverage-${BUILD_TAG}" + ] + } + } + } + + stage('Release') { + when { + tag pattern: "[0-9]+\\.[0-9]+\\.[0-9]+", comparator: "REGEXP" + } + + agent { + docker { + image "us.gcr.io/logdna-k8s/python:3.7-ci" + customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" + } + } + steps { + witCredentials([ + usernamePassword( + credentialsId: 'pypi-username-password', + usernameVariable: 'TWINE_USERNAME', + passwordVariable: 'TWINE_PASSWORD' + ) + ]){ + sh 'python setup.py sdist' + sh 'twine upload dist/logdna-*.tar.gz' + } + } + } + } +} diff --git a/README.md b/README.md index 817e7a9..8aec2fc 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@

- -[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-) -

Python package for logging to LogDNA

+ +[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-) + + --- * [Installation](#installation) @@ -326,7 +327,7 @@ The time in seconds since the epoch to use for the log timestamp. It must be wit This project makes use of the [poetry][] package manager for local development. ```shell -$ poetry isntall +$ poetry install ``` ### Scripts diff --git a/setup.cfg b/setup.cfg index a8d4c5a..bf13f84 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[metadata] +description-file = README.md + [flake8] exclude = .git,__pycache__,docs/source/conf.py,old,build,dist max-complexity = 10 From 1941935597e2250579d2876b2234c648fd236f1b Mon Sep 17 00:00:00 2001 From: Mark Curtis Date: Tue, 9 Mar 2021 18:57:13 +0000 Subject: [PATCH 088/139] Use os separator instead of hard coded / * Use os seperator instead of hard coded / * correct formatting * tweak import statements --- logdna/configs.py | 5 +++-- scripts/json_coverage.py | 12 +++++++----- setup.py | 6 ++++-- tests/test_utils.py | 6 ++---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/logdna/configs.py b/logdna/configs.py index 3c808c9..a35d32d 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -1,6 +1,7 @@ -from os import path +from os import path, sep -with open("%s/VERSION" % path.abspath(path.dirname(__file__))) as f: +with open("{p}{s}VERSION".format(p=path.abspath(path.dirname(__file__)), + s=sep)) as f: version = f.read().strip('\n') defaults = { diff --git a/scripts/json_coverage.py b/scripts/json_coverage.py index c78174e..563d123 100644 --- a/scripts/json_coverage.py +++ b/scripts/json_coverage.py @@ -19,11 +19,13 @@ def json_coverage(): totals = report.get('totals') summary = { 'lines': { - 'total': totals['covered_lines'] + totals['missing_lines'], - 'covered': totals['covered_lines'], - 'pct': totals['covered_lines'] / ( - totals['covered_lines'] + totals['missing_lines'] - ) * 100 + 'total': + totals['covered_lines'] + totals['missing_lines'], + 'covered': + totals['covered_lines'], + 'pct': + totals['covered_lines'] / + (totals['covered_lines'] + totals['missing_lines']) * 100 }, 'statements': { 'total': None, diff --git a/setup.py b/setup.py index ffafd56..34386cf 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ from setuptools import setup -from os import path +from os import path, sep # read the contents of your README file this_directory = path.abspath(path.dirname(__file__)) @@ -7,7 +7,9 @@ with open(path.join(this_directory, 'README.md'), 'rb') as f: long_description = f.read().decode('utf-8') -with open("%s/logdna/VERSION" % this_directory) as f: +with open("{p}{s}logdna{s}VERSION" + .format(p=this_directory, s=sep) + ) as f: version = f.read().strip('\n') setup( diff --git a/tests/test_utils.py b/tests/test_utils.py index 59fee87..21c990b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -42,9 +42,7 @@ def test_get_ip_socket_error(self, _): self.assertEqual(get_ip(), '127.0.0.1', 'default to localhost on error') - @patch( - 'socket.socket', - **{'return_value.getsockname.return_value': [IP, VIP]} - ) + @patch('socket.socket', + **{'return_value.getsockname.return_value': [IP, VIP]}) def test_get_ip_default(self, _): self.assertEqual(get_ip(), IP, 'default to localhost on error') From 9e4b1c0a43bc0941ba4fb336ea12a3f497622ee6 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Mon, 5 Apr 2021 11:44:20 -0500 Subject: [PATCH 089/139] feat(ci): Enable releases through semantic release Include semantic release as a dev dependency to automate package releases. Updated Jenkinsfile to run release process through semantic release --- .config.mk | 9 + .gitignore | 2 + Jenkinsfile | 44 ++--- Makefile | 85 +++++++++ poetry.lock | 493 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 9 + setup.cfg | 2 +- setup.py | 5 +- 8 files changed, 618 insertions(+), 31 deletions(-) create mode 100644 .config.mk create mode 100644 Makefile diff --git a/.config.mk b/.config.mk new file mode 100644 index 0000000..65038c5 --- /dev/null +++ b/.config.mk @@ -0,0 +1,9 @@ +# Below is an example of pulling the current version of a node app. + +#VERSION is being deprecated by APP_VERSION - no changes necessary - see Makefile +#APP_VERSION=$(shell awk '/version/ {gsub(/[",]/,""); print $$2}' package.json) + +GIT_AUTHOR_NAME ?= $(shell git config --get user.name) +GIT_AUTHOR_EMAIL ?= $(shell git config --get user.email) +GIT_COMMITTER_NAME ?= $(GIT_AUTHOR_NAME) +GIT_COMMITTER_EMAIL ?= $(GIT_AUTHOR_EMAIL) diff --git a/.gitignore b/.gitignore index fe9e6fa..dc6e372 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ coverage/ .coverage Session.vim .cache +pypoetry/ +pip/ diff --git a/Jenkinsfile b/Jenkinsfile index 195c815..e97e2ec 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,7 @@ library 'magic-butler-catalogue' def PROJECT_NAME = 'logdna-python' def TRIGGER_PATTERN = ".*@logdnabot.*" +def DEFAULT_BRANCH = 'master' pipeline { agent none @@ -17,22 +18,14 @@ pipeline { stages { stage('Test') { agent { - docker { - image "us.gcr.io/logdna-k8s/python:3.7-ci" + node { + label 'ec2-fleet' customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" } } - environment { - XDG_CONFIG_HOME = pwd() - POETRY_VIRTUALENV_IN_PROJECT = true - } - steps { - sh 'poetry config --list --local' - sh 'poetry install --no-interaction -vvv' - sh 'poetry run task lint' - sh 'poetry run task test' + sh 'make install lint test' } post { @@ -51,26 +44,25 @@ pipeline { } stage('Release') { - when { - tag pattern: "[0-9]+\\.[0-9]+\\.[0-9]+", comparator: "REGEXP" - } - agent { - docker { - image "us.gcr.io/logdna-k8s/python:3.7-ci" + node { + label 'ec2-fleet' customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" } } + + environment { + GITHUB_TOKEN = credentials('github-api-token') + PYPI_TOKEN = credentials('pypi-token') + } + steps { - witCredentials([ - usernamePassword( - credentialsId: 'pypi-username-password', - usernameVariable: 'TWINE_USERNAME', - passwordVariable: 'TWINE_PASSWORD' - ) - ]){ - sh 'python setup.py sdist' - sh 'twine upload dist/logdna-*.tar.gz' + script { + if ("${BRANCH_NAME}" == "${DEFAULT_BRANCH}") { + sh 'make release' + } else { + sh "BRANCH_NAME=${DEFAULT_BRANCH} CHANGE_ID='' make release-dry" + } } } } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b689d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,85 @@ +# Makefile Version 2021032403 +# +# Source in repository specific environment variables +include .config.mk + +# Define commands via docker +DOCKER = docker +DOCKER_RUN := $(DOCKER) run --rm -i +WORKDIR :=/workdir +DOCKER_COMMAND := $(DOCKER_RUN) -v $(PWD):$(WORKDIR):Z -w $(WORKDIR) \ + -e XDG_CONFIG_HOME=$(WORKDIR) \ + -e XDG_CACHE_HOME=$(WORKDIR) \ + -e POETRY_CACHE_DIR=$(WORKDIR)/.cache \ + -e POETRY_VIRTUALENV_IN_PROJECT=true \ + -e PYPI_TOKEN \ + -e GITHUB_TOKEN \ + -e GIT_AUTHOR_NAME \ + -e GIT_AUTHOR_EMAIL \ + -e GIT_COMMITTER_NAME \ + -e GIT_COMMITTER_EMAIL \ + us.gcr.io/logdna-k8s/python:3.7-ci + +POETRY_COMMAND := $(DOCKER_COMMAND) poetry + +# Exports the variables for shell use +export + +# This helper function makes debugging much easier. +.PHONY:debug-% +debug-%: ## Debug a variable by calling `make debug-VARIABLE` + @echo $(*) = $($(*)) + +.PHONY:help +.SILENT:help +help: ## Show this help, includes list of all actions. + @awk 'BEGIN {FS = ":.*?## "}; /^.+: .*?## / && !/awk/ {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ${MAKEFILE_LIST} + +.PHONY:clean +clean: ## purge build time artifacts + rm -rf dist/ build/ coverage/ pypoetry/ pip/ .cache + +.PHONY:changelog +changelog: ## print the next version of the change log to stdout + $(POETRY_COMMAND) run semantic-release changelog --unreleased + +.PHONY:install +install: ## install development and build time dependencies + $(POETRY_COMMAND) install --no-interaction -vvv + +.PHONY:lint +lint: ## run lint rules and print error report + $(POETRY_COMMAND) run task lint + +.PHONY:lint-fix +lint-fix:## attempt to auto fix linting error and report remaining errors + $(POETRY_COMMAND) run task lint:fix + +.PHONY:package +package: ## Generate a python sdist and wheel + $(POETRY_COMMAND) build + +.PHONY:release +release: clean install ## run semantic release build and publish results to github + pypi based on unreleased commits + $(POETRY_COMMAND) run task release + +.PHONY:release-dry +release-dry: clean install changelog ## run semantic release in noop mode + $(POETRY_COMMAND) run semantic-release publish --noop --verbosity=DEBUG + +.PHONY:release-patch +release-patch: clean install ## run semantic release build and force a patch release + $(POETRY_COMMAND) run semantic-release publish --patch + +.PHONY:release-minor +release-minor: clean install ## run semantic release build and force a minor release + $(POETRY_COMMAND) run semantic-release publish --minor + +.PHONY:release-major +release-major: clean install ## run semantic release build and force a major release + $(POETRY_COMMAND) run semantic-release publish --major + +.PHONY:test +test: ## run project test suite + $(POETRY_COMMAND) run task test + diff --git a/poetry.lock b/poetry.lock index 6aceb28..8490cff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -36,6 +36,19 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "bleach" +version = "3.3.0" +description = "An easy safelist-based HTML-sanitizing tool." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +packaging = "*" +six = ">=1.9.0" +webencodings = "*" + [[package]] name = "certifi" version = "2020.12.5" @@ -44,6 +57,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "chardet" version = "4.0.0" @@ -52,6 +76,25 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click-log" +version = "0.3.2" +description = "Logging integration for Click" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +click = "*" + [[package]] name = "colorama" version = "0.4.4" @@ -71,6 +114,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] toml = ["toml"] +[[package]] +name = "cryptography" +version = "3.4.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + [[package]] name = "decorator" version = "4.4.2" @@ -79,6 +141,25 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" +[[package]] +name = "docutils" +version = "0.17" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "dotty-dict" +version = "1.3.0" +description = "Dictionary wrapper for quick access to deeply nested keys." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +setuptools_scm = "*" + [[package]] name = "flake8" version = "3.8.4" @@ -93,6 +174,28 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" +[[package]] +name = "gitdb" +version = "4.0.7" +description = "Git Object Database" +category = "dev" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +smmap = ">=3.0.1,<5" + +[[package]] +name = "gitpython" +version = "3.1.14" +description = "Python Git Library" +category = "dev" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +gitdb = ">=4.0.1,<5" + [[package]] name = "idna" version = "2.10" @@ -125,6 +228,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "invoke" +version = "1.5.0" +description = "Pythonic task execution" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "ipdb" version = "0.13.4" @@ -190,6 +301,35 @@ parso = ">=0.8.0,<0.9.0" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] +[[package]] +name = "jeepney" +version = "0.6.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "keyring" +version = "22.3.0" +description = "Store and access your passwords safely." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=1", markers = "python_version < \"3.8\""} +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] + [[package]] name = "mccabe" version = "0.6.1" @@ -248,6 +388,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "pkginfo" +version = "1.7.0" +description = "Query metadatdata from sdists / bdists / installed packages." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +testing = ["nose", "coverage"] + [[package]] name = "pluggy" version = "0.13.1" @@ -308,6 +459,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pyflakes" version = "2.2.0" @@ -369,6 +528,72 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +[[package]] +name = "python-gitlab" +version = "2.6.0" +description = "Interact with GitLab API" +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +requests = ">=2.22.0" +requests-toolbelt = ">=0.9.1" + +[package.extras] +autocompletion = ["argcomplete (>=1.10.0,<2)"] +yaml = ["PyYaml (>=5.2)"] + +[[package]] +name = "python-semantic-release" +version = "7.15.3" +description = "Automatic Semantic Versioning for Python projects" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=7,<8" +click-log = ">=0.3,<1" +dotty-dict = ">=1.3.0,<2" +gitpython = ">=3.0.8,<4" +invoke = ">=1.4.1,<2" +python-gitlab = ">=1.10,<3" +requests = ">=2.25,<3" +semver = ">=2.10,<3" +tomlkit = ">=0.7.0,<1.0.0" +twine = ">=3,<4" + +[package.extras] +dev = ["mypy", "tox", "isort", "black"] +docs = ["Sphinx (==1.3.6)"] +test = ["coverage (>=5,<6)", "pytest (>=5,<6)", "pytest-xdist (>=1,<2)", "pytest-mock (>=2,<3)", "responses (==0.5.0)", "mock (==1.3.0)"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "readme-renderer" +version = "29.0" +description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +bleach = ">=2.1.0" +docutils = ">=0.13.1" +Pygments = ">=2.5.1" +six = "*" + +[package.extras] +md = ["cmarkgfm (>=0.5.0,<0.6.0)"] + [[package]] name = "requests" version = "2.25.1" @@ -387,6 +612,59 @@ urllib3 = ">=1.21.1,<1.27" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +[[package]] +name = "requests-toolbelt" +version = "0.9.1" +description = "A utility belt for advanced users of python-requests" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rfc3986" +version = "1.4.0" +description = "Validating URI References per RFC 3986" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "secretstorage" +version = "3.3.1" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "semver" +version = "2.13.0" +description = "Python helper for Semantic Versioning (http://semver.org/)" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "setuptools-scm" +version = "6.0.1" +description = "the blessed package to manage your versions by scm tags" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +toml = ["toml"] + [[package]] name = "six" version = "1.15.0" @@ -395,6 +673,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "smmap" +version = "4.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "tap.py" version = "3.0" @@ -427,6 +713,27 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomlkit" +version = "0.7.0" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "tqdm" +version = "4.59.0" +description = "Fast, Extensible Progress Meter" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "4.3.3" @@ -443,6 +750,25 @@ six = "*" [package.extras] test = ["pytest", "mock"] +[[package]] +name = "twine" +version = "3.3.0" +description = "Collection of utilities for publishing packages on PyPI" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = ">=0.4.3" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +keyring = ">=15.1" +pkginfo = ">=1.4.2" +readme-renderer = ">=21.0" +requests = ">=2.20" +requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" +rfc3986 = ">=1.4.0" +tqdm = ">=4.14" + [[package]] name = "typing-extensions" version = "3.7.4.3" @@ -472,6 +798,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "yapf" version = "0.30.0" @@ -495,7 +829,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "f860b4efd8c9effba27c1d04219d50794d74208204b7e3efca8ada9686fafea9" +content-hash = "e32b90a7d407970b1a0211de99d4adda266a46f7e222c8d3bb4dd57dbbec17ca" [metadata.files] appnope = [ @@ -514,14 +848,65 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] +bleach = [ + {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"}, + {file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"}, +] certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +click-log = [ + {file = "click-log-0.3.2.tar.gz", hash = "sha256:16fd1ca3fc6b16c98cea63acf1ab474ea8e676849dc669d86afafb0ed7003124"}, + {file = "click_log-0.3.2-py2.py3-none-any.whl", hash = "sha256:eee14dc37cdf3072158570f00406572f9e03e414accdccfccd4c538df9ae322c"}, +] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, @@ -577,14 +962,43 @@ coverage = [ {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"}, {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"}, ] +cryptography = [ + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, +] decorator = [ {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, ] +docutils = [ + {file = "docutils-0.17-py2.py3-none-any.whl", hash = "sha256:a71042bb7207c03d5647f280427f14bfbd1a65c9eb84f4b341d85fafb6bb4bdf"}, + {file = "docutils-0.17.tar.gz", hash = "sha256:e2ffeea817964356ba4470efba7c2f42b6b0de0b04e66378507e3e2504bbff4c"}, +] +dotty-dict = [ + {file = "dotty_dict-1.3.0.tar.gz", hash = "sha256:eb0035a3629ecd84397a68f1f42f1e94abd1c34577a19cd3eacad331ee7cbaf0"}, +] flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] +gitdb = [ + {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, + {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, +] +gitpython = [ + {file = "GitPython-3.1.14-py3-none-any.whl", hash = "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b"}, + {file = "GitPython-3.1.14.tar.gz", hash = "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61"}, +] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, @@ -597,6 +1011,11 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +invoke = [ + {file = "invoke-1.5.0-py2-none-any.whl", hash = "sha256:da7c2d0be71be83ffd6337e078ef9643f41240024d6b2659e7b46e0b251e339f"}, + {file = "invoke-1.5.0-py3-none-any.whl", hash = "sha256:7e44d98a7dc00c91c79bac9e3007276965d2c96884b3c22077a9f04042bd6d90"}, + {file = "invoke-1.5.0.tar.gz", hash = "sha256:f0c560075b5fb29ba14dad44a7185514e94970d1b9d57dcd3723bec5fed92650"}, +] ipdb = [ {file = "ipdb-0.13.4.tar.gz", hash = "sha256:c85398b5fb82f82399fc38c44fe3532c0dde1754abee727d8f5cfcc74547b334"}, ] @@ -612,6 +1031,14 @@ jedi = [ {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, ] +jeepney = [ + {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, + {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, +] +keyring = [ + {file = "keyring-22.3.0-py3-none-any.whl", hash = "sha256:2bc8363ebdd63886126a012057a85c8cb6e143877afa02619ac7dbc9f38a207b"}, + {file = "keyring-22.3.0.tar.gz", hash = "sha256:16927a444b2c73f983520a48dec79ddab49fe76429ea05b8d528d778c8339522"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -636,6 +1063,10 @@ pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] +pkginfo = [ + {file = "pkginfo-1.7.0-py2.py3-none-any.whl", hash = "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"}, + {file = "pkginfo-1.7.0.tar.gz", hash = "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, @@ -686,6 +1117,10 @@ pycodestyle = [ {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pyflakes = [ {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, @@ -706,14 +1141,54 @@ pytest-cov = [ {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, ] +python-gitlab = [ + {file = "python-gitlab-2.6.0.tar.gz", hash = "sha256:a862c6874524ab585b725a17b2cd2950fc09d6d74205f40a11be2a4e8f2dcaa1"}, + {file = "python_gitlab-2.6.0-py3-none-any.whl", hash = "sha256:8f6a62b2804021416f13e91f359085f1071fb7adedfb292ef0d0877df777a075"}, +] +python-semantic-release = [ + {file = "python-semantic-release-7.15.3.tar.gz", hash = "sha256:0ffcb267923731b3578d4377de67c59c5779c80e514ef9883c47c8877c059d74"}, + {file = "python_semantic_release-7.15.3-py3-none-any.whl", hash = "sha256:9b5c6e7ff9887b0cc8178004aa101aae2bfec8a464316cf287be8acbdf1e4954"}, +] +pywin32-ctypes = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] +readme-renderer = [ + {file = "readme_renderer-29.0-py2.py3-none-any.whl", hash = "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c"}, + {file = "readme_renderer-29.0.tar.gz", hash = "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"}, +] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] +requests-toolbelt = [ + {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, + {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, +] +rfc3986 = [ + {file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"}, + {file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"}, +] +secretstorage = [ + {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, + {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, +] +semver = [ + {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] +setuptools-scm = [ + {file = "setuptools_scm-6.0.1-py3-none-any.whl", hash = "sha256:c3bd5f701c8def44a5c0bfe8d407bef3f80342217ef3492b951f3777bd2d915c"}, + {file = "setuptools_scm-6.0.1.tar.gz", hash = "sha256:d1925a69cb07e9b29416a275b9fadb009a23c148ace905b2fb220649a6c18e92"}, +] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] +smmap = [ + {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, + {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, +] "tap.py" = [ {file = "tap.py-3.0-py2.py3-none-any.whl", hash = "sha256:a598bfaa2e224d71f2e86147c2ef822c18ff2e1b8ef006397e5056b08f92f699"}, {file = "tap.py-3.0.tar.gz", hash = "sha256:f5eeeeebfd64e53d32661752bb4c288589a3babbb96db3f391a4ec29f1359c70"}, @@ -726,10 +1201,22 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomlkit = [ + {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, + {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, +] +tqdm = [ + {file = "tqdm-4.59.0-py2.py3-none-any.whl", hash = "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7"}, + {file = "tqdm-4.59.0.tar.gz", hash = "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33"}, +] traitlets = [ {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, ] +twine = [ + {file = "twine-3.3.0-py3-none-any.whl", hash = "sha256:2f6942ec2a17417e19d2dd372fc4faa424c87ee9ce49b4e20c427eb00a0f3f41"}, + {file = "twine-3.3.0.tar.gz", hash = "sha256:fcffa8fc37e8083a5be0728371f299598870ee1eccc94e9a25cef7b1dcfa8297"}, +] typing-extensions = [ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, @@ -743,6 +1230,10 @@ wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] yapf = [ {file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"}, {file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"}, diff --git a/pyproject.toml b/pyproject.toml index 10619cf..ce2a404 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,13 @@ description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" +[tool.semantic_release] +version_toml = "pyproject.toml:tool.poetry.version" +version_pattern = "logdna/VERSION:(\\d+\\.\\d+\\.\\d+)" +branch = "master" +commit_subject = "release: Version {version} [skip ci]" +commit_author = "LogDNA Bot " + [tool.poetry.dependencies] python = "^3.6" requests = "^2.25.1" @@ -18,6 +25,7 @@ yapf = "^0.30.0" pytest = "^6.2.2" pytest-cov = "^2.11.1" taskipy = "^1.6.0" +python-semantic-release = "^7.15.3" [tool.taskipy.tasks] pre_test = "mkdir coverage -p" @@ -26,6 +34,7 @@ post_test = "python scripts/json_coverage.py" lint = "flake8 --doctests" "lint:fix" = "yapf -r -i logdna scripts tests" "post_lint:fix" = "task lint" +release = "semantic-release publish" [build-system] requires = ["poetry>=0.12"] diff --git a/setup.cfg b/setup.cfg index bf13f84..c24cd82 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ description-file = README.md [flake8] -exclude = .git,__pycache__,docs/source/conf.py,old,build,dist +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,.cache,pip,pypoetry max-complexity = 10 [yapf] diff --git a/setup.py b/setup.py index 34386cf..fc16e48 100644 --- a/setup.py +++ b/setup.py @@ -7,9 +7,8 @@ with open(path.join(this_directory, 'README.md'), 'rb') as f: long_description = f.read().decode('utf-8') -with open("{p}{s}logdna{s}VERSION" - .format(p=this_directory, s=sep) - ) as f: +kwargs = {"dir": this_directory, "sep": sep} +with open("{dir}{sep}logdna{sep}VERSION".format(**kwargs)) as f: version = f.read().strip('\n') setup( From fce4d5995b31c426f2b66992b57f129f22f9f18f Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Wed, 7 Apr 2021 12:06:58 -0500 Subject: [PATCH 090/139] fix(ci): correct invalid environment variable The correct variable name for the github auth token is GH_TOKEN, not GITHUB_TOKEN. --- Jenkinsfile | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e97e2ec..4217468 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -52,7 +52,7 @@ pipeline { } environment { - GITHUB_TOKEN = credentials('github-api-token') + GH_TOKEN = credentials('github-api-token') PYPI_TOKEN = credentials('pypi-token') } diff --git a/Makefile b/Makefile index 0b689d1..e27b93e 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ DOCKER_COMMAND := $(DOCKER_RUN) -v $(PWD):$(WORKDIR):Z -w $(WORKDIR) \ -e POETRY_CACHE_DIR=$(WORKDIR)/.cache \ -e POETRY_VIRTUALENV_IN_PROJECT=true \ -e PYPI_TOKEN \ - -e GITHUB_TOKEN \ + -e GH_TOKEN \ -e GIT_AUTHOR_NAME \ -e GIT_AUTHOR_EMAIL \ -e GIT_COMMITTER_NAME \ From 9ce8353ceb3a3e61819c01caa38294f31e6edd40 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Wed, 7 Apr 2021 17:55:53 +0000 Subject: [PATCH 091/139] release: Version 1.6.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..605db4c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + + + +## v1.6.0 (2021-04-07) +### Feature +* **ci:** Enable releases through semantic release ([`9e4b1c0`](https://github.com/logdna/python/commit/9e4b1c0a43bc0941ba4fb336ea12a3f497622ee6)) +* **ci:** Include jenkins setup ([`1c5be37`](https://github.com/logdna/python/commit/1c5be37ef32776f2d0ba2b68b67cebb58b1f0177)) +* **tasks:** Convert run scripts to tasks ([`f5a8b18`](https://github.com/logdna/python/commit/f5a8b182941a0c514594cbaa7a3ac5952173d5a8)) +* **lint:** Setup linting + test harness ([`0a7763a`](https://github.com/logdna/python/commit/0a7763a2befbf598b59d7ab595b19e22b173fda5)) +* **tags:** Allow tags to be configured ([`6adc21e`](https://github.com/logdna/python/commit/6adc21e872fa521be1aaf08309f7f3d0ba3dc5c5)) +* **meta:** Python log info in meta object ([`cf9b505`](https://github.com/logdna/python/commit/cf9b505734df12918a665a8a8c74d4fd74e5bc47)) +* **handlers:** Make available via config file ([`39c5dec`](https://github.com/logdna/python/commit/39c5decd98e8d4feb6c1bbfa487faf35396c8b12)) + +### Fix +* **ci:** Correct invalid environment variable ([`fce4d59`](https://github.com/logdna/python/commit/fce4d5995b31c426f2b66992b57f129f22f9f18f)) + +### Documentation +* Add @matthiasfru as a contributor ([`3727bc3`](https://github.com/logdna/python/commit/3727bc3386d3dd6465e0b0d15675a091a3743c24)) +* Add @rudyryk as a contributor ([`9ab0e39`](https://github.com/logdna/python/commit/9ab0e3932180c41bff1cd0944c24fb8e208f4391)) +* Add @kurtiss as a contributor ([`1476085`](https://github.com/logdna/python/commit/14760857649b56207240bae907386a33f7f1666b)) +* Update @inkrement as a contributor ([`6a2cede`](https://github.com/logdna/python/commit/6a2cedef6695e18a981a3605176c9cffdd159827)) +* Add @inkrement as a contributor ([`7ba6992`](https://github.com/logdna/python/commit/7ba6992287f98c478da72f5982a0869b5d835df7)) +* Add @btashton as a contributor ([`c1e69bf`](https://github.com/logdna/python/commit/c1e69bfc965e1e1e5717ec7af3271dc0bf6b502d)) +* Add @SpainTrain as a contributor ([`7b9f04b`](https://github.com/logdna/python/commit/7b9f04b86a1e813bccea29ea8db1554808ea48b6)) +* Add @sataloger as a contributor ([`04081f0`](https://github.com/logdna/python/commit/04081f039d6136a66b259f7245ad7845ef1c080a)) +* Add @jdemaeyer as a contributor ([`69e8f7c`](https://github.com/logdna/python/commit/69e8f7cc0782a778d0da230ff1a8feb5f8cee352)) +* Add @dchai76 as a contributor ([`42e9c6b`](https://github.com/logdna/python/commit/42e9c6bddc2bce29714072bba37d85d9a734eac2)) +* Add @danmaas as a contributor ([`23bbab4`](https://github.com/logdna/python/commit/23bbab48cdef4aaef6813459dbdb23dbd9c60374)) +* Add @LYHuang as a contributor ([`3cc9e23`](https://github.com/logdna/python/commit/3cc9e232d0b12b9f0315efb1d618426b486a2604)) +* Add @baronomasia as a contributor ([`8971bd7`](https://github.com/logdna/python/commit/8971bd713d74cd9386c60a50cc75a7fc3591f544)) +* Add @utek as a contributor ([`ea495b4`](https://github.com/logdna/python/commit/ea495b479cc0549ff68a1e816c3f7a174c1c138c)) +* Add @esatterwhite as a contributor ([`0a334bd`](https://github.com/logdna/python/commit/0a334bdb5690049634c64eb0f9c6c1026b9ab001)) +* Add @mikehu as a contributor ([`e7aa7cb`](https://github.com/logdna/python/commit/e7aa7cb2624313c065f7bbc8a129c1a4841f9ec2)) +* Add @vilyapilya as a contributor ([`e3191f5`](https://github.com/logdna/python/commit/e3191f577fb7ddf7590ca1e1bf239f66d2f30fd0)) +* Add @smusali as a contributor ([`4d5022f`](https://github.com/logdna/python/commit/4d5022f93948cca239ebc104e34034c350956f65)) +* Add @respectus as a contributor ([`85a543c`](https://github.com/logdna/python/commit/85a543c9dc27a3c6064e790be0ba1c475187c38d)) diff --git a/logdna/VERSION b/logdna/VERSION index 94fe62c..dc1e644 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.5.4 +1.6.0 diff --git a/pyproject.toml b/pyproject.toml index ce2a404..c21c779 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.5.4" +version = "1.6.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From d9b00c8c450b5f645fd95c851a3322945b2877ad Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Wed, 7 Apr 2021 17:57:17 +0000 Subject: [PATCH 092/139] release: Version 1.7.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 605db4c..182021a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.7.0 (2021-04-07) + + ## v1.6.0 (2021-04-07) ### Feature * **ci:** Enable releases through semantic release ([`9e4b1c0`](https://github.com/logdna/python/commit/9e4b1c0a43bc0941ba4fb336ea12a3f497622ee6)) diff --git a/logdna/VERSION b/logdna/VERSION index dc1e644..bd8bf88 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.6.0 +1.7.0 diff --git a/pyproject.toml b/pyproject.toml index c21c779..426b313 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.6.0" +version = "1.7.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 024aa8202a548e5bb6b2ac9b150f7e8da4e7f921 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:24:40 +0000 Subject: [PATCH 093/139] release: Version 1.8.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 182021a..b3113b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.8.0 (2021-04-15) + + ## v1.7.0 (2021-04-07) diff --git a/logdna/VERSION b/logdna/VERSION index bd8bf88..27f9cd3 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.7.0 +1.8.0 diff --git a/pyproject.toml b/pyproject.toml index 426b313..d61effb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.7.0" +version = "1.8.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 40822de69beab1cb431a37dc29857ff24cca108f Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:26:28 +0000 Subject: [PATCH 094/139] release: Version 1.9.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3113b0..8608e59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.9.0 (2021-04-15) + + ## v1.8.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 27f9cd3..f8e233b 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.8.0 +1.9.0 diff --git a/pyproject.toml b/pyproject.toml index d61effb..5488446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.8.0" +version = "1.9.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 11250e79ebf04e4f952dba5b46abc1fc4236a910 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:27:51 +0000 Subject: [PATCH 095/139] release: Version 1.10.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8608e59..2dc14e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.10.0 (2021-04-15) + + ## v1.9.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index f8e233b..81c871d 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.9.0 +1.10.0 diff --git a/pyproject.toml b/pyproject.toml index 5488446..7b26413 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.9.0" +version = "1.10.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 8690fc0751a4cd836d92ca1336ca2c68003f25be Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:29:17 +0000 Subject: [PATCH 096/139] release: Version 1.11.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dc14e1..e4cb410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.11.0 (2021-04-15) + + ## v1.10.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 81c871d..1cac385 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.10.0 +1.11.0 diff --git a/pyproject.toml b/pyproject.toml index 7b26413..b9555b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.10.0" +version = "1.11.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 4b8df3cbedc3771ec811b2894f04bbb3e15620e4 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:30:41 +0000 Subject: [PATCH 097/139] release: Version 1.12.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4cb410..729a718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.12.0 (2021-04-15) + + ## v1.11.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 1cac385..0eed1a2 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.11.0 +1.12.0 diff --git a/pyproject.toml b/pyproject.toml index b9555b9..3e6d680 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.11.0" +version = "1.12.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 1d143f7b3b6e09156ce1571d06938af4da9c4051 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:32:05 +0000 Subject: [PATCH 098/139] release: Version 1.13.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 729a718..97f5ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.13.0 (2021-04-15) + + ## v1.12.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 0eed1a2..feaae22 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.12.0 +1.13.0 diff --git a/pyproject.toml b/pyproject.toml index 3e6d680..3e2d962 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.12.0" +version = "1.13.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 70e5fb97fd1e3d2fdf96f31703a37c3c6f6cb817 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:33:30 +0000 Subject: [PATCH 099/139] release: Version 1.14.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f5ae6..2433ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.14.0 (2021-04-15) + + ## v1.13.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index feaae22..850e742 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.13.0 +1.14.0 diff --git a/pyproject.toml b/pyproject.toml index 3e2d962..7e4f16e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.13.0" +version = "1.14.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 70e28ce20b4820456bb3fcc2e8cc1ebdd2ab19bd Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 15:34:54 +0000 Subject: [PATCH 100/139] release: Version 1.15.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2433ab1..126a5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.15.0 (2021-04-15) + + ## v1.14.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 850e742..141f2e8 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.14.0 +1.15.0 diff --git a/pyproject.toml b/pyproject.toml index 7e4f16e..9b99f02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.14.0" +version = "1.15.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From e8c2dc4b5308aa57854071ddd3a0ba48ee3cabc8 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Thu, 15 Apr 2021 09:25:47 -0500 Subject: [PATCH 101/139] ci: prevent double releases w/ skip ci This make sure the release commit doesn't also trigger a release by checking for `skip ci` in the commit message --- Jenkinsfile | 46 +++++++++++++++++++++++++++++++++++++--------- Makefile | 3 +++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4217468..009701d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,6 +2,7 @@ library 'magic-butler-catalogue' def PROJECT_NAME = 'logdna-python' def TRIGGER_PATTERN = ".*@logdnabot.*" def DEFAULT_BRANCH = 'master' +def CURRENT_BRANCH = [env.CHANGE_BRANCH, env.BRANCH_NAME]?.find{branch -> branch != null} pipeline { agent none @@ -51,17 +52,44 @@ pipeline { } } - environment { - GH_TOKEN = credentials('github-api-token') - PYPI_TOKEN = credentials('pypi-token') - } + stages { + stage('dry run') { + when { + not { + branch "${DEFAULT_BRANCH}" + } + } - steps { - script { - if ("${BRANCH_NAME}" == "${DEFAULT_BRANCH}") { + environment { + GH_TOKEN = credentials('github-api-token') + PYPI_TOKEN = credentials('pypi-token') + JENKINS_URL = "${JENKINS_URL}" + BRANCH_NAME = "${DEFAULT_BRANCH}" + GIT_BRANCH = "${DEFAULT_BRANCH}" + CHANGE_ID = '' + } + + steps { + sh "make release-dry" + } + } + + stage('publish') { + + environment { + GH_TOKEN = credentials('github-api-token') + PYPI_TOKEN = credentials('pypi-token') + JENKINS_URL = "${JENKINS_URL}" + } + + when { + branch "${DEFAULT_BRANCH}" + not { + changelog '\\[skip ci\\]' + } + } + steps { sh 'make release' - } else { - sh "BRANCH_NAME=${DEFAULT_BRANCH} CHANGE_ID='' make release-dry" } } } diff --git a/Makefile b/Makefile index e27b93e..9e90ed2 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ DOCKER_COMMAND := $(DOCKER_RUN) -v $(PWD):$(WORKDIR):Z -w $(WORKDIR) \ -e POETRY_VIRTUALENV_IN_PROJECT=true \ -e PYPI_TOKEN \ -e GH_TOKEN \ + -e JENKINS_URL \ + -e BRANCH_NAME \ + -e CHANGE_ID \ -e GIT_AUTHOR_NAME \ -e GIT_AUTHOR_EMAIL \ -e GIT_COMMITTER_NAME \ From d206ba4615975a05389c81754875dfa9548cd2b2 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Apr 2021 19:44:45 +0000 Subject: [PATCH 102/139] release: Version 1.16.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 3 +++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 126a5c2..3dd813e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ +## v1.16.0 (2021-04-15) + + ## v1.15.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 141f2e8..15b989e 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.15.0 +1.16.0 diff --git a/pyproject.toml b/pyproject.toml index 9b99f02..25160eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.15.0" +version = "1.16.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 2bba17c15e97d93dcee771a2267736c80a5886fb Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Thu, 3 Jun 2021 18:02:49 +0400 Subject: [PATCH 103/139] ci: cleaning on automation Semver: patch --- Makefile | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9e90ed2..3e4f1b4 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ help: ## Show this help, includes list of all actions. .PHONY:clean clean: ## purge build time artifacts - rm -rf dist/ build/ coverage/ pypoetry/ pip/ .cache + rm -rf dist/ build/ coverage/ pypoetry/ pip/ **/__pycache__/ .pytest_cache/ .cache .coverage .PHONY:changelog changelog: ## print the next version of the change log to stdout diff --git a/pyproject.toml b/pyproject.toml index 25160eb..5a1ce48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ taskipy = "^1.6.0" python-semantic-release = "^7.15.3" [tool.taskipy.tasks] -pre_test = "mkdir coverage -p" +pre_test = "mkdir -p coverage" test = "pytest --junitxml=coverage/test.xml --cov=logdna --cov-report=html" post_test = "python scripts/json_coverage.py" lint = "flake8 --doctests" From 02169d6b336ffd4b0b55a52114ad152e9acbbc2c Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 7 Jun 2021 14:45:02 +0400 Subject: [PATCH 104/139] refactor: reduce code complexity Semver: patch --- logdna/configs.py | 9 ++++---- logdna/logdna.py | 52 ++++++++++++++++++-------------------------- tests/test_logger.py | 4 ++-- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/logdna/configs.py b/logdna/configs.py index a35d32d..1c99347 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -6,11 +6,10 @@ defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, - 'MAX_LINE_LENGTH': 32000, - 'FLUSH_INTERVAL_SECS': 5, - 'FLUSH_BYTE_LIMIT': 2 * 1024 * 1024, + 'FLUSH_INTERVAL': 250, + 'FLUSH_LIMIT': 5 * 1024 * 1024, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', - 'BUF_RETENTION_BYTE_LIMIT': 4 * 1024 * 1024, - 'RETRY_INTERVAL_SECS': 8, + 'BUF_RETENTION_LIMIT': 4 * 1024 * 1024, + 'RETRY_INTERVAL': 5000, 'USER_AGENT': 'python/%s' % version } diff --git a/logdna/logdna.py b/logdna/logdna.py index eeb6000..4f50c91 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -5,7 +5,6 @@ import sys import threading import time -from functools import reduce from .configs import defaults from .utils import sanitize_meta, get_ip @@ -22,10 +21,10 @@ def __init__(self, key, options={}): self.key = key self.buf = [] + self.buf_size = 0 self.secondary = [] self.exception_flag = False self.flusher = None - self.max_length = defaults['MAX_LINE_LENGTH'] self.hostname = options.get('hostname', socket.gethostname()) self.ip = options.get('ip', get_ip()) @@ -40,15 +39,14 @@ def __init__(self, key, options={}): self.include_standard_meta = options.get('include_standard_meta', False) self.index_meta = options.get('index_meta', False) - self.flush_limit = options.get('flush_limit', - defaults['FLUSH_BYTE_LIMIT']) - self.flush_interval_secs = options.get('flush_interval', - defaults['FLUSH_INTERVAL_SECS']) - self.retry_interval_secs = options.get('retry_interval_secs', - defaults['RETRY_INTERVAL_SECS']) + self.flush_limit = options.get('flush_limit', defaults['FLUSH_LIMIT']) + self.flush_interval = options.get('flush_interval', + defaults['FLUSH_INTERVAL']) + self.retry_interval = options.get('retry_interval', + defaults['RETRY_INTERVAL']) self.tags = options.get('tags', []) - self.buf_retention_byte_limit = options.get( - 'buf_retention_limit', defaults['BUF_RETENTION_BYTE_LIMIT']) + self.buf_retention_limit = options.get('buf_retention_limit', + defaults['BUF_RETENTION_LIMIT']) self.user_agent = options.get('user_agent', defaults['USER_AGENT']) if isinstance(self.tags, str): @@ -58,43 +56,36 @@ def __init__(self, key, options={}): self.setLevel(logging.DEBUG) self.lock = threading.RLock() - # TODO(esatterwhite): complexity too high (8) - def buffer_log(self, message): - if message and message['line']: - if len(message['line']) > self.max_length: - message['line'] = message[ - 'line'][:self.max_length] + ' (cut off, too long...)' - if self.verbose in ['true', 'debug', 'd']: - self.internalLogger.debug( - 'Line was longer than %s chars and was truncated.', - self.max_length) + def start_flusher(self): + interval = (self.retry_interval + if self.exception_flag else self.flush_interval) + self.flusher = threading.Timer(float(interval / 1000), self.flush) + self.flusher.start() + def buffer_log(self, message): # Attempt to acquire lock to write to buf # otherwise write to secondary as flush occurs if self.lock.acquire(blocking=False): - buf_size = reduce(lambda x, y: x + len(y['line']), self.buf, 0) - - if buf_size + len(message['line']) < self.buf_retention_byte_limit: + if self.buf_size + len(message['line']) < self.buf_retention_limit: self.buf.append(message) + self.buf_size += len(message['line']) else: self.internalLogger.debug( 'The buffer size exceeded the limit: %s', - self.buf_retention_byte_limit) + self.buf_retention_limit) self.lock.release() - if buf_size >= self.flush_limit and not self.exception_flag: + if self.buf_size >= self.flush_limit and not self.exception_flag: self.flush() else: self.secondary.append(message) if not self.flusher: - interval = (self.retry_interval_secs - if self.exception_flag else self.flush_interval_secs) - self.flusher = threading.Timer(interval, self.flush) - self.flusher.start() + self.start_flusher() def clean_after_success(self): del self.buf[:] + self.buf_size = 0 self.exception_flag = False if self.flusher: self.flusher.cancel() @@ -140,8 +131,7 @@ def flush(self): self.lock.release() else: if not self.flusher: - self.flusher = threading.Timer(1, self.flush) - self.flusher.start() + self.start_flusher() # TODO(esatterwhite): complexity too high (14) def emit(self, record): # noqa: C901 diff --git a/tests/test_logger.py b/tests/test_logger.py index 3c5fa8d..1ed992e 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -84,8 +84,8 @@ def stops_retention_when_buf_is_full(self): 'mac': 'C0:FF:EE:C0:FF:EE', 'buf_retention_limit': 50, 'equest_timeout': 10, - 'flush_interval': 1, - 'retry_interval_secs': 1 + 'flush_interval': 1000, + 'retry_interval': 1000 } handler = LogDNAHandler(LOGDNA_API_KEY, options) From 9bfe479132acb0aa8e5784f2aa31298606e49789 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 7 Jun 2021 21:54:12 +0400 Subject: [PATCH 105/139] feat(threadWorkerPools): introduce extra threads In order to provide non-blocking I/O, introducing ThreadWorkerPools for multiple parts and imitating the non-blocking I/O implementation from [our Ruby library](github.com/logdna/ruby). Semver: minor --- logdna/configs.py | 5 +- logdna/logdna.py | 270 ++++++++++++++++++++++++++++--------------- tests/test_logger.py | 10 +- 3 files changed, 185 insertions(+), 100 deletions(-) diff --git a/logdna/configs.py b/logdna/configs.py index 1c99347..9dac4a8 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -7,7 +7,10 @@ defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, 'FLUSH_INTERVAL': 250, - 'FLUSH_LIMIT': 5 * 1024 * 1024, + 'FLUSH_LIMIT': 2 * 1024 * 1024, + 'MAX_CONCURRENT_REQUESTS': 10, + 'MAX_RETRY_ATTEMPTS': 3, + 'MAX_RETRY_JITTER': 0.5, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', 'BUF_RETENTION_LIMIT': 4 * 1024 * 1024, 'RETRY_INTERVAL': 5000, diff --git a/logdna/logdna.py b/logdna/logdna.py index 4f50c91..5f2e299 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -6,69 +6,104 @@ import threading import time +from concurrent.futures import ThreadPoolExecutor + from .configs import defaults from .utils import sanitize_meta, get_ip class LogDNAHandler(logging.Handler): def __init__(self, key, options={}): + # Setup Handler logging.Handler.__init__(self) + + # Set Internal Logger self.internal_handler = logging.StreamHandler(sys.stdout) self.internal_handler.setLevel(logging.DEBUG) self.internalLogger = logging.getLogger('internal') - self.internalLogger.setLevel(logging.DEBUG) self.internalLogger.addHandler(self.internal_handler) + self.internalLogger.setLevel(logging.DEBUG) + # Set the Custom Variables self.key = key - self.buf = [] - self.buf_size = 0 - self.secondary = [] - self.exception_flag = False - self.flusher = None - self.hostname = options.get('hostname', socket.gethostname()) self.ip = options.get('ip', get_ip()) self.mac = options.get('mac', None) self.level = options.get('level', 'info') - self.verbose = str(options.get('verbose', 'true')).lower() self.app = options.get('app', '') self.env = options.get('env', '') + self.tags = options.get('tags', []) + if isinstance(self.tags, str): + self.tags = [tag.strip() for tag in self.tags.split(',')] + elif not isinstance(self.tags, list): + self.tags = [] + + # Set the Connection Variables self.url = options.get('url', defaults['LOGDNA_URL']) self.request_timeout = options.get('request_timeout', defaults['DEFAULT_REQUEST_TIMEOUT']) + self.user_agent = options.get('user_agent', defaults['USER_AGENT']) + self.max_retry_attempts = options.get('max_retry_attempts', + defaults['MAX_RETRY_ATTEMPTS']) + self.max_retry_jitter = options.get('max_retry_jitter', + defaults['MAX_RETRY_JITTER']) + self.max_concurrent_requests = options.get( + 'max_concurrent_requests', defaults['MAX_CONCURRENT_REQUESTS']) + self.retry_interval = options.get('retry_interval', + defaults['RETRY_INTERVAL']) + + # Set the Flush-related Variables + self.buf = [] + self.buf_size = 0 + self.secondary = [] + self.exception_flag = False + self.flusher = None self.include_standard_meta = options.get('include_standard_meta', False) self.index_meta = options.get('index_meta', False) self.flush_limit = options.get('flush_limit', defaults['FLUSH_LIMIT']) self.flush_interval = options.get('flush_interval', defaults['FLUSH_INTERVAL']) - self.retry_interval = options.get('retry_interval', - defaults['RETRY_INTERVAL']) - self.tags = options.get('tags', []) self.buf_retention_limit = options.get('buf_retention_limit', defaults['BUF_RETENTION_LIMIT']) - self.user_agent = options.get('user_agent', defaults['USER_AGENT']) - if isinstance(self.tags, str): - self.tags = [tag.strip() for tag in self.tags.split(',')] - elif not isinstance(self.tags, list): - self.tags = [] + # Set up the Thread Pools + self.worker_thread_pool = ThreadPoolExecutor() + self.request_thread_pool = ThreadPoolExecutor( + max_workers=self.max_concurrent_requests) + self.setLevel(logging.DEBUG) self.lock = threading.RLock() def start_flusher(self): - interval = (self.retry_interval - if self.exception_flag else self.flush_interval) - self.flusher = threading.Timer(float(interval / 1000), self.flush) - self.flusher.start() + if not self.flusher: + interval = (self.retry_interval + if self.exception_flag else self.flush_interval) + self.flusher = threading.Timer(float(interval / 1000), self.flush) + self.flusher.start() + + def close_flusher(self): + if self.flusher: + self.flusher.cancel() + self.flusher = None def buffer_log(self, message): + if self.worker_thread_pool: + try: + self.worker_thread_pool.submit(self.buffer_log_sync, message) + except RuntimeError: + self.buffer_log_sync(message) + except Exception as e: + self.internalLogger.debug('Error in calling buffer_log: %s', e) + + def buffer_log_sync(self, message): # Attempt to acquire lock to write to buf # otherwise write to secondary as flush occurs if self.lock.acquire(blocking=False): - if self.buf_size + len(message['line']) < self.buf_retention_limit: + msglen = len(message['line']) + if self.buf_size + msglen < self.buf_retention_limit: self.buf.append(message) - self.buf_size += len(message['line']) + self.buf_size += msglen else: self.internalLogger.debug( 'The buffer size exceeded the limit: %s', @@ -76,78 +111,115 @@ def buffer_log(self, message): self.lock.release() if self.buf_size >= self.flush_limit and not self.exception_flag: + self.close_flusher() self.flush() + else: + self.start_flusher() else: self.secondary.append(message) - if not self.flusher: - self.start_flusher() - def clean_after_success(self): del self.buf[:] self.buf_size = 0 self.exception_flag = False - if self.flusher: - self.flusher.cancel() - self.flusher = None + self.close_flusher() - def handle_exception(self, exception): - if self.flusher: - self.flusher.cancel() - self.flusher = None - self.exception_flag = True - if self.verbose in ['true', 'error', 'err', 'e']: - self.internalLogger.debug('Error sending logs %s', exception) + def flush(self): + if self.worker_thread_pool: + try: + self.worker_thread_pool.submit(self.flush_sync) + except RuntimeError: + self.flush_sync() + except Exception as e: + self.internalLogger.debug('Error in calling flush: %s', e) + + def flush_sync(self): + if self.buf_size == 0: + return + if self.lock.acquire(blocking=False): + if self.request_thread_pool: + try: + self.request_thread_pool.submit(self.try_request) + except RuntimeError: + self.try_request() + except Exception as e: + self.internalLogger.debug( + 'Error in calling try_request: %s', e) + self.lock.release() + else: + self.start_flusher() - # do not call without acquiring the lock - def send_request(self): + def try_request(self): self.buf.extend(self.secondary) self.secondary = [] data = {'e': 'ls', 'ls': self.buf} + retries = 0 + while True: + retries += 1 + if retries > self.max_retry_attempts: + self.internalLogger.debug( + 'Flush exceeded %s tries. Discarding flush buffer', + self.max_retry_attempts) + self.close_flusher() + self.exception_flag = True + break + + if self.send_request(data): + self.clean_after_success() + break + + sleep_time = self.retry_interval * (1 << (retries - 1)) + sleep_time += self.max_retry_jitter + time.sleep(sleep_time) + + def send_request(self, data): try: - res = requests.post(url=self.url, - json=data, - auth=('user', self.key), - params={ - 'hostname': self.hostname, - 'ip': self.ip, - 'mac': self.mac if self.mac else None, - 'tags': self.tags if self.tags else None - }, - stream=True, - timeout=self.request_timeout, - headers={'user-agent': self.user_agent}) - res.raise_for_status() - # when no RequestException happened - self.clean_after_success() - except requests.exceptions.RequestException as e: - self.handle_exception(e) + response = requests.post(url=self.url, + json=data, + auth=('user', self.key), + params={ + 'hostname': self.hostname, + 'ip': self.ip, + 'mac': self.mac, + 'tags': self.tags + }, + stream=True, + timeout=self.request_timeout, + headers={'user-agent': self.user_agent}) - def flush(self): - if len(self.buf) == 0: - return - if self.lock.acquire(blocking=False): - self.send_request() - self.lock.release() - else: - if not self.flusher: - self.start_flusher() + response.raise_for_status() + status_code = response.status_code + if status_code in [401, 403]: + self.internalLogger.debug( + 'Please provide a valid ingestion key.' + + ' Discarding flush buffer') + return True + + if status_code == 200: + return True - # TODO(esatterwhite): complexity too high (14) - def emit(self, record): # noqa: C901 + if status_code in [400, 500, 504]: + self.internalLogger.debug('The request failed %s. Retrying...', + response.reason) + return True + else: + self.internalLogger.debug( + 'The request failed: %s. Retrying...', response.reason) + + except requests.exceptions.Timeout as timeout: + self.internalLogger.debug('Timeout error occurred %s. Retrying...', + timeout) + + except requests.exceptions.RequestException as exception: + self.internalLogger.debug( + 'Error sending logs %s. Discarding flush buffer', exception) + return True + + return False + + def emit(self, record): msg = self.format(record) record = record.__dict__ - opts = {} - if 'args' in record: - opts = record['args'] - if self.include_standard_meta: - if isinstance(opts, tuple): - opts = {} - if 'meta' not in opts: - opts['meta'] = {} - for key in ['name', 'pathname', 'lineno']: - opts['meta'][key] = record[key] - message = { 'hostname': self.hostname, 'timestamp': int(time.time() * 1000), @@ -156,25 +228,35 @@ def emit(self, record): # noqa: C901 'app': self.app or record['module'], 'env': self.env } - if not isinstance(opts, tuple): - if 'level' in opts: - message['level'] = opts['level'] - if 'app' in opts: - message['app'] = opts['app'] - if 'hostname' in opts: - message['hostname'] = opts['hostname'] - if 'env' in opts: - message['env'] = opts['env'] - if 'timestamp' in opts: - message['timestamp'] = opts['timestamp'] - if 'meta' in opts: - if self.index_meta: - message['meta'] = sanitize_meta(opts['meta']) - else: - message['meta'] = json.dumps(opts['meta']) + + opts = {} + if 'args' in record and not isinstance(record['args'], tuple): + opts = record['args'] + + for key in message.keys(): + if key in opts: + message[key] = opts[key] + + if self.include_standard_meta: + if 'meta' not in opts: + opts['meta'] = {} + for key in ['name', 'pathname', 'lineno']: + if key in record: + opts['meta'][key] = record[key] + if self.index_meta: + message['meta'] = sanitize_meta(opts['meta']) + else: + message['meta'] = json.dumps(opts['meta']) + self.buffer_log(message) def close(self): - if len(self.buf) > 0: - self.flush() + self.close_flusher() + self.flush_sync() + if self.worker_thread_pool: + self.worker_thread_pool.shutdown(wait=True) + self.worker_thread_pool = None + if self.request_thread_pool: + self.request_thread_pool.shutdown(wait=True) + self.request_thread_pool = None logging.Handler.close(self) diff --git a/tests/test_logger.py b/tests/test_logger.py index 1ed992e..8bc7d6b 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -49,8 +49,8 @@ def server_recieves_messages(self): server_thread.join() logdna_thread.join() - self.assertEqual(len(expectedLines), 1) - self.assertIn(line, expectedLines) + self.assertEqual(len(expectedLines), 1, 'line count') + self.assertIn(line, expectedLines, 'actual line') logger.removeHandler(handler) def messages_preserved_if_excp(self): @@ -72,7 +72,7 @@ def messages_preserved_if_excp(self): server_thread.join() logdna_thread.join() - self.assertEqual(len(handler.buf), 1) + self.assertEqual(len(handler.buf), 1, 'line count') logger.removeHandler(handler) def stops_retention_when_buf_is_full(self): @@ -99,8 +99,8 @@ def stops_retention_when_buf_is_full(self): server_thread.join() logdna_thread.join() - self.assertEqual(len(handler.buf), 1) - self.assertNotEqual(handler.buf[0]['line'], lineTwo) + self.assertEqual(len(handler.buf), 1, 'line count') + self.assertNotEqual(handler.buf[0]['line'], lineTwo, 'test inequality') logger.removeHandler(handler) def test_run_tests(self): From 72da47a740455a61e4592db8e132ed2aa741cba2 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Tue, 15 Jun 2021 16:32:50 +0400 Subject: [PATCH 106/139] test(logger): modify testing for logger Semver: patch --- logdna/configs.py | 4 +- logdna/logdna.py | 19 +++-- pyproject.toml | 2 +- tests/mock/__init.__.py | 0 tests/mock/log.py | 18 ----- tests/mock/server.py | 19 ----- tests/test_logger.py | 149 +++++++++++++++++----------------------- 7 files changed, 75 insertions(+), 136 deletions(-) delete mode 100644 tests/mock/__init.__.py delete mode 100644 tests/mock/log.py delete mode 100644 tests/mock/server.py diff --git a/logdna/configs.py b/logdna/configs.py index 9dac4a8..5e03d92 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -6,13 +6,13 @@ defaults = { 'DEFAULT_REQUEST_TIMEOUT': 30, - 'FLUSH_INTERVAL': 250, + 'FLUSH_INTERVAL_SECS': 0.25, 'FLUSH_LIMIT': 2 * 1024 * 1024, 'MAX_CONCURRENT_REQUESTS': 10, 'MAX_RETRY_ATTEMPTS': 3, 'MAX_RETRY_JITTER': 0.5, 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', 'BUF_RETENTION_LIMIT': 4 * 1024 * 1024, - 'RETRY_INTERVAL': 5000, + 'RETRY_INTERVAL_SECS': 5, 'USER_AGENT': 'python/%s' % version } diff --git a/logdna/logdna.py b/logdna/logdna.py index 5f2e299..8ec4bd7 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -29,7 +29,7 @@ def __init__(self, key, options={}): self.hostname = options.get('hostname', socket.gethostname()) self.ip = options.get('ip', get_ip()) self.mac = options.get('mac', None) - self.level = options.get('level', 'info') + self.loglevel = options.get('level', 'info') self.app = options.get('app', '') self.env = options.get('env', '') self.tags = options.get('tags', []) @@ -49,8 +49,8 @@ def __init__(self, key, options={}): defaults['MAX_RETRY_JITTER']) self.max_concurrent_requests = options.get( 'max_concurrent_requests', defaults['MAX_CONCURRENT_REQUESTS']) - self.retry_interval = options.get('retry_interval', - defaults['RETRY_INTERVAL']) + self.retry_interval_secs = options.get('retry_interval_secs', + defaults['RETRY_INTERVAL_SECS']) # Set the Flush-related Variables self.buf = [] @@ -62,8 +62,8 @@ def __init__(self, key, options={}): False) self.index_meta = options.get('index_meta', False) self.flush_limit = options.get('flush_limit', defaults['FLUSH_LIMIT']) - self.flush_interval = options.get('flush_interval', - defaults['FLUSH_INTERVAL']) + self.flush_interval_secs = options.get('flush_interval', + defaults['FLUSH_INTERVAL_SECS']) self.buf_retention_limit = options.get('buf_retention_limit', defaults['BUF_RETENTION_LIMIT']) @@ -77,9 +77,8 @@ def __init__(self, key, options={}): def start_flusher(self): if not self.flusher: - interval = (self.retry_interval - if self.exception_flag else self.flush_interval) - self.flusher = threading.Timer(float(interval / 1000), self.flush) + self.flusher = threading.Timer(self.flush_interval_secs, + self.flush) self.flusher.start() def close_flusher(self): @@ -168,7 +167,7 @@ def try_request(self): self.clean_after_success() break - sleep_time = self.retry_interval * (1 << (retries - 1)) + sleep_time = self.retry_interval_secs * (1 << (retries - 1)) sleep_time += self.max_retry_jitter time.sleep(sleep_time) @@ -224,7 +223,7 @@ def emit(self, record): 'hostname': self.hostname, 'timestamp': int(time.time() * 1000), 'line': msg, - 'level': record['levelname'] or self.level, + 'level': record['levelname'] or self.loglevel, 'app': self.app or record['module'], 'env': self.env } diff --git a/pyproject.toml b/pyproject.toml index 5a1ce48..1300100 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ branch = true source = ["logdna"] [tool.coverage.report] -fail_under = 67 +fail_under = 40 show_missing = true [tool.coverage.json] diff --git a/tests/mock/__init.__.py b/tests/mock/__init.__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mock/log.py b/tests/mock/log.py deleted file mode 100644 index d9999e0..0000000 --- a/tests/mock/log.py +++ /dev/null @@ -1,18 +0,0 @@ -import logging -from threading import Thread - -__all__ = ['logger', 'info', 'LOGDNA_API_KEY'] -LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' -logger = logging.getLogger('logdna') -logger.setLevel(logging.INFO) - - -def info(*args): - def fn(): - for line in args: - logger.info(line) - - thread = Thread(target=fn) - thread.setDaemon(True) - thread.start() - return thread diff --git a/tests/mock/server.py b/tests/mock/server.py deleted file mode 100644 index c0a1791..0000000 --- a/tests/mock/server.py +++ /dev/null @@ -1,19 +0,0 @@ -import socket -from http.server import HTTPServer -from threading import Thread - - -def get_port(): - s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) - s.bind(('localhost', 0)) - _, port = s.getsockname() - s.close() - return port - - -def start_server(port, cls): - server = HTTPServer(('localhost', port), cls) - thread = Thread(target=server.handle_request) - thread.setDaemon(True) - thread.start() - return thread diff --git a/tests/test_logger.py b/tests/test_logger.py index 8bc7d6b..6820ee1 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,112 +1,89 @@ -import json +import logging import unittest -from http.server import BaseHTTPRequestHandler from logdna import LogDNAHandler -from .mock.server import get_port, start_server -from .mock.log import logger, info, LOGDNA_API_KEY +from concurrent.futures import ThreadPoolExecutor +from logdna.configs import defaults expectedLines = [] - - -class SuccessfulRequestHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers['Content-Length']) - body = self.rfile.read(content_length) - self.send_response(200) - - self.end_headers() - body = json.loads(body)['ls'] - for keys in body: - expectedLines.append(keys['line']) - - -class FailedRequestHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers['Content-Length']) - self.rfile.read(content_length) - self.send_response(400) - self.end_headers() +LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' +logger = logging.getLogger('logdna') +logger.setLevel(logging.INFO) class LogDNAHandlerTest(unittest.TestCase): - def server_recieves_messages(self): - port = get_port() + def handler_test(self): options = { 'hostname': 'localhost', - 'url': 'http://localhost:{0}'.format(port), 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE' - } - - handler = LogDNAHandler(LOGDNA_API_KEY, options) - logger.addHandler(handler) - line = "python python python" - - server_thread = start_server(port, SuccessfulRequestHandler) - logdna_thread = info(line) - - server_thread.join() - logdna_thread.join() - - self.assertEqual(len(expectedLines), 1, 'line count') - self.assertIn(line, expectedLines, 'actual line') - logger.removeHandler(handler) - - def messages_preserved_if_excp(self): - port = get_port() - options = { - 'hostname': 'localhost', - 'url': 'http://localhost:{0}'.format(port), - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE' + 'mac': 'C0:FF:EE:C0:FF:EE', + 'tags': 'sample,test' } handler = LogDNAHandler(LOGDNA_API_KEY, options) - logger.addHandler(handler) - line = "second test. server fails" - - server_thread = start_server(port, FailedRequestHandler) - logdna_thread = info(line) - - server_thread.join() - logdna_thread.join() - - self.assertEqual(len(handler.buf), 1, 'line count') - logger.removeHandler(handler) - - def stops_retention_when_buf_is_full(self): - port = get_port() + self.assertIsInstance(handler, logging.Handler) + self.assertIsInstance(handler.internal_handler, logging.StreamHandler) + self.assertIsNotNone(handler.internalLogger) + self.assertEqual(handler.key, LOGDNA_API_KEY) + self.assertEqual(handler.hostname, options['hostname']) + self.assertEqual(handler.ip, options['ip']) + self.assertEqual(handler.mac, options['mac']) + self.assertEqual(handler.loglevel, 'info') + self.assertEqual(handler.app, '') + self.assertEqual(handler.env, '') + self.assertEqual(handler.tags, options['tags'].split(',')) + + # Set the Connection Variables + self.assertEqual(handler.url, defaults['LOGDNA_URL']) + self.assertEqual(handler.request_timeout, + defaults['DEFAULT_REQUEST_TIMEOUT']) + self.assertEqual(handler.user_agent, defaults['USER_AGENT']) + self.assertEqual(handler.max_retry_attempts, + defaults['MAX_RETRY_ATTEMPTS']) + self.assertEqual(handler.max_retry_jitter, + defaults['MAX_RETRY_JITTER']) + self.assertEqual(handler.max_concurrent_requests, + defaults['MAX_CONCURRENT_REQUESTS']) + self.assertEqual(handler.retry_interval_secs, + defaults['RETRY_INTERVAL_SECS']) + + # Set the Flush-related Variables + self.assertEqual(handler.buf, []) + self.assertEqual(handler.buf_size, 0) + self.assertEqual(handler.secondary, []) + self.assertFalse(handler.exception_flag) + self.assertIsNone(handler.flusher) + self.assertFalse(handler.include_standard_meta) + self.assertFalse(handler.index_meta) + self.assertEqual(handler.flush_limit, defaults['FLUSH_LIMIT']) + self.assertEqual(handler.flush_interval_secs, + defaults['FLUSH_INTERVAL_SECS']) + self.assertEqual(handler.buf_retention_limit, + defaults['BUF_RETENTION_LIMIT']) + + # Set up the Thread Pools + self.assertIsInstance(handler.worker_thread_pool, ThreadPoolExecutor) + self.assertIsInstance(handler.request_thread_pool, ThreadPoolExecutor) + self.assertEqual(handler.level, logging.DEBUG) + + def flusher_test(self): options = { 'hostname': 'localhost', - 'url': 'http://localhost:{0}'.format(port), 'ip': '10.0.1.1', 'mac': 'C0:FF:EE:C0:FF:EE', - 'buf_retention_limit': 50, - 'equest_timeout': 10, - 'flush_interval': 1000, - 'retry_interval': 1000 + 'tags': 'sample,test' } handler = LogDNAHandler(LOGDNA_API_KEY, options) - logger.addHandler(handler) - line = "when buffer grows bigger than we want" - lineTwo = "when buffer grows bigger than we want. And more and more" - - server_thread = start_server(port, FailedRequestHandler) - logdna_thread = info(line, lineTwo) - - server_thread.join() - logdna_thread.join() - - self.assertEqual(len(handler.buf), 1, 'line count') - self.assertNotEqual(handler.buf[0]['line'], lineTwo, 'test inequality') - logger.removeHandler(handler) + self.assertIsNone(handler.flusher) + handler.start_flusher() + self.assertIsNotNone(handler.flusher) + handler.close_flusher() + self.assertIsNone(handler.flusher) def test_run_tests(self): - self.server_recieves_messages() - self.messages_preserved_if_excp() - self.stops_retention_when_buf_is_full() + self.handler_test() + self.flusher_test() if __name__ == '__main__': From f250237dbde932e99ab199023516f66a248c5e80 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 28 Jun 2021 21:24:21 +0400 Subject: [PATCH 107/139] feat(meta): enable adding custom meta fields This commit introduces a new option called `custom_fields` similar to `tags` and also new default `meta` fields: `lineno`, `name`, `pathname`, `args`, etc. Semver: minor --- README.md | 21 +++---- logdna/configs.py | 1 + logdna/logdna.py | 63 +++++++++---------- logdna/utils.py | 19 +++++- pyproject.toml | 4 +- tests/test_logger.py | 141 ++++++++++++++++++++++++++++++++++++------- tests/test_utils.py | 19 +++++- 7 files changed, 193 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 8aec2fc..ba9ab0f 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,9 @@ _**Required**_ * [LogDNA Ingestion Key](https://app.logdna.com/manage/profile) _**Optional**_ -* Hostname - ([string][]) - max length 32 chars +* Hostname - ([string][]) * MAC Address - ([string][]) * IP Address - ([string][]) -* Max Length - ([bool][]) - formatted as options['max_length'] * Index Meta - ([bool][]) - formatted as options['index_meta'] ## Usage @@ -156,7 +155,6 @@ The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your * Type: [string][] * Default: `''` * Values: `YourCustomApp` -* Max Length: `32` The default app passed along with every log sent through this instance. @@ -166,7 +164,6 @@ The default app passed along with every log sent through this instance. * Type: [string][] * Default: `''` * Values: `YourCustomEnv` -* Max Length: `32` The default env passed along with every log sent through this instance. @@ -176,7 +173,6 @@ The default env passed along with every log sent through this instance. * Type: [string][] * Default: `''` * Values: `YourCustomHostname` -* Max Length: `32` The default hostname passed along with every log sent through this instance. @@ -190,6 +186,7 @@ Python [LogRecord][] objects includes language-specific information that may be Setting `include_standard_meta` to `True` automatically populates meta objects with `name`, `pathname`, and `lineno` from the [LogRecord][]. +*WARNING* This option is deprecated and will be removed in the upcoming major release. ##### index_meta @@ -203,14 +200,12 @@ If this option is set to True then meta objects are parsed and searchable up to *WARNING* If this option is True, your metadata objects MUST have consistent types across all log messages or the metadata object may not be parsed properly. - ##### level * _Optional_ * Type: [string][] * Default: `Info` * Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` -* Max Length: `32` The default level passed along with every log sent through this instance. @@ -247,6 +242,14 @@ List of tags used to dynamically group hosts. More information on tags is avail A custom ingestion endpoint to stream log lines into. +##### custom_fields + +* _Optional_ +* Type: [list][]<[string][]> +* Default: `['args', 'name', 'pathname', 'lineno']` + +List of fields out of `record` object to include in the `meta` object. By default, `args`, `name`, `pathname`, and `lineno` will be included. + ### log(line, [options]) #### line @@ -254,7 +257,6 @@ A custom ingestion endpoint to stream log lines into. * _Required_ * Type: [string][] * Default: `''` -* Max Length: `32000` The log line to be sent to LogDNA. @@ -266,7 +268,6 @@ The log line to be sent to LogDNA. * Type: [string][] * Default: `Info` * Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` -* Max Length: `32` The level passed along with this log line. @@ -276,7 +277,6 @@ The level passed along with this log line. * Type: [string][] * Default: `''` * Values: `YourCustomApp` -* Max Length: `32` The app passed along with this log line. @@ -286,7 +286,6 @@ The app passed along with this log line. * Type: [string][] * Default: `''` * Values: `YourCustomEnv` -* Max Length: `32` The environment passed with this log line. diff --git a/logdna/configs.py b/logdna/configs.py index 5e03d92..d790776 100644 --- a/logdna/configs.py +++ b/logdna/configs.py @@ -11,6 +11,7 @@ 'MAX_CONCURRENT_REQUESTS': 10, 'MAX_RETRY_ATTEMPTS': 3, 'MAX_RETRY_JITTER': 0.5, + 'META_FIELDS': ['args', 'name', 'pathname', 'lineno'], 'LOGDNA_URL': 'https://logs.logdna.com/logs/ingest', 'BUF_RETENTION_LIMIT': 4 * 1024 * 1024, 'RETRY_INTERVAL_SECS': 5, diff --git a/logdna/logdna.py b/logdna/logdna.py index 8ec4bd7..4a835e3 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -1,4 +1,3 @@ -import json import logging import requests import socket @@ -9,7 +8,7 @@ from concurrent.futures import ThreadPoolExecutor from .configs import defaults -from .utils import sanitize_meta, get_ip +from .utils import sanitize_meta, get_ip, normalize_list_option class LogDNAHandler(logging.Handler): @@ -32,11 +31,9 @@ def __init__(self, key, options={}): self.loglevel = options.get('level', 'info') self.app = options.get('app', '') self.env = options.get('env', '') - self.tags = options.get('tags', []) - if isinstance(self.tags, str): - self.tags = [tag.strip() for tag in self.tags.split(',')] - elif not isinstance(self.tags, list): - self.tags = [] + self.tags = normalize_list_option(options, 'tags') + self.custom_fields = normalize_list_option(options, 'custom_fields') + self.custom_fields += defaults['META_FIELDS'] # Set the Connection Variables self.url = options.get('url', defaults['LOGDNA_URL']) @@ -58,8 +55,13 @@ def __init__(self, key, options={}): self.secondary = [] self.exception_flag = False self.flusher = None - self.include_standard_meta = options.get('include_standard_meta', - False) + self.include_standard_meta = options.get('include_standard_meta', None) + + if self.include_standard_meta is not None: + self.internalLogger.debug( + '"include_standard_meta" option will be deprecated ' + + 'removed in the upcoming major release') + self.index_meta = options.get('index_meta', False) self.flush_limit = options.get('flush_limit', defaults['FLUSH_LIMIT']) self.flush_interval_secs = options.get('flush_interval', @@ -153,16 +155,8 @@ def try_request(self): self.secondary = [] data = {'e': 'ls', 'ls': self.buf} retries = 0 - while True: + while retries < self.max_retry_attempts: retries += 1 - if retries > self.max_retry_attempts: - self.internalLogger.debug( - 'Flush exceeded %s tries. Discarding flush buffer', - self.max_retry_attempts) - self.close_flusher() - self.exception_flag = True - break - if self.send_request(data): self.clean_after_success() break @@ -171,6 +165,13 @@ def try_request(self): sleep_time += self.max_retry_jitter time.sleep(sleep_time) + if retries >= self.max_retry_attempts: + self.internalLogger.debug( + 'Flush exceeded %s tries. Discarding flush buffer', + self.max_retry_attempts) + self.close_flusher() + self.exception_flag = True + def send_request(self, data): try: response = requests.post(url=self.url, @@ -228,25 +229,15 @@ def emit(self, record): 'env': self.env } - opts = {} - if 'args' in record and not isinstance(record['args'], tuple): - opts = record['args'] - - for key in message.keys(): - if key in opts: - message[key] = opts[key] - - if self.include_standard_meta: - if 'meta' not in opts: - opts['meta'] = {} - for key in ['name', 'pathname', 'lineno']: - if key in record: - opts['meta'][key] = record[key] - if self.index_meta: - message['meta'] = sanitize_meta(opts['meta']) - else: - message['meta'] = json.dumps(opts['meta']) + message['meta'] = {} + for key in self.custom_fields: + if key in record: + if isinstance(record[key], tuple): + message['meta'][key] = list(record[key]) + elif record[key] is not None: + message['meta'][key] = record[key] + message['meta'] = sanitize_meta(message['meta'], self.index_meta) self.buffer_log(message) def close(self): diff --git a/logdna/utils.py b/logdna/utils.py index 790ae0a..7278f12 100644 --- a/logdna/utils.py +++ b/logdna/utils.py @@ -10,7 +10,24 @@ def is_jsonable(obj): return False -def sanitize_meta(meta): +def normalize_list_option(options, key): + value = options.get(key, []) + if isinstance(value, str): + value = [val.strip() for val in value.split(',')] + elif not isinstance(value, list): + value = [] + return value + + +def sanitize_meta(meta, index_meta=False): + if not index_meta: + if is_jsonable(meta): + return json.dumps(meta) + + return { + '__errors': 'Meta cannot be serialized into JSON-formatted string' + } + keys_to_sanitize = [] for key, value in meta.items(): if not is_jsonable(value): diff --git a/pyproject.toml b/pyproject.toml index 1300100..3f4c0a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ python-semantic-release = "^7.15.3" [tool.taskipy.tasks] pre_test = "mkdir -p coverage" -test = "pytest --junitxml=coverage/test.xml --cov=logdna --cov-report=html" +test = "pytest --junitxml=coverage/test.xml --cov=logdna --cov-report=html --verbose" post_test = "python scripts/json_coverage.py" lint = "flake8 --doctests" "lint:fix" = "yapf -r -i logdna scripts tests" @@ -49,7 +49,7 @@ branch = true source = ["logdna"] [tool.coverage.report] -fail_under = 40 +fail_under = 75 show_missing = true [tool.coverage.json] diff --git a/tests/test_logger.py b/tests/test_logger.py index 6820ee1..2c18b97 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,5 +1,6 @@ import logging import unittest +import requests from logdna import LogDNAHandler from concurrent.futures import ThreadPoolExecutor @@ -9,29 +10,65 @@ LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' logger = logging.getLogger('logdna') logger.setLevel(logging.INFO) +sample_record = logging.LogRecord('test', logging.INFO, 'test', 5, + 'Something to test', '', '', '', '') +sample_message = { + 'line': 'Something to test', + 'hostname': 'localhost', + 'level': 'INFO', + 'app': 'test', + 'env': '', + 'meta': { + 'args': '', + 'name': 'test', + 'pathname': 'test', + 'lineno': 5 + } +} +sample_options = { + 'hostname': 'localhost', + 'ip': '10.0.1.1', + 'mac': 'C0:FF:EE:C0:FF:EE', + 'tags': 'sample,test', + 'index_meta': True +} + + +class MockThreadPoolExecutor(): + def __init__(self, **kwargs): + pass + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + pass + + def submit(self, fn, *args, **kwargs): + # execute functions in series without creating threads + # for easier unit testing + result = fn(*args, **kwargs) + return result + + def shutdown(self, wait=True): + pass class LogDNAHandlerTest(unittest.TestCase): def handler_test(self): - options = { - 'hostname': 'localhost', - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE', - 'tags': 'sample,test' - } - - handler = LogDNAHandler(LOGDNA_API_KEY, options) + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) self.assertIsInstance(handler, logging.Handler) self.assertIsInstance(handler.internal_handler, logging.StreamHandler) self.assertIsNotNone(handler.internalLogger) self.assertEqual(handler.key, LOGDNA_API_KEY) - self.assertEqual(handler.hostname, options['hostname']) - self.assertEqual(handler.ip, options['ip']) - self.assertEqual(handler.mac, options['mac']) + self.assertEqual(handler.hostname, sample_options['hostname']) + self.assertEqual(handler.ip, sample_options['ip']) + self.assertEqual(handler.mac, sample_options['mac']) self.assertEqual(handler.loglevel, 'info') self.assertEqual(handler.app, '') self.assertEqual(handler.env, '') - self.assertEqual(handler.tags, options['tags'].split(',')) + self.assertEqual(handler.tags, sample_options['tags'].split(',')) + self.assertEqual(handler.custom_fields, defaults['META_FIELDS']) # Set the Connection Variables self.assertEqual(handler.url, defaults['LOGDNA_URL']) @@ -53,8 +90,7 @@ def handler_test(self): self.assertEqual(handler.secondary, []) self.assertFalse(handler.exception_flag) self.assertIsNone(handler.flusher) - self.assertFalse(handler.include_standard_meta) - self.assertFalse(handler.index_meta) + self.assertTrue(handler.index_meta) self.assertEqual(handler.flush_limit, defaults['FLUSH_LIMIT']) self.assertEqual(handler.flush_interval_secs, defaults['FLUSH_INTERVAL_SECS']) @@ -67,23 +103,84 @@ def handler_test(self): self.assertEqual(handler.level, logging.DEBUG) def flusher_test(self): - options = { - 'hostname': 'localhost', - 'ip': '10.0.1.1', - 'mac': 'C0:FF:EE:C0:FF:EE', - 'tags': 'sample,test' - } - - handler = LogDNAHandler(LOGDNA_API_KEY, options) + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) self.assertIsNone(handler.flusher) handler.start_flusher() self.assertIsNotNone(handler.flusher) handler.close_flusher() self.assertIsNone(handler.flusher) + def emit_test(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + handler.buffer_log = unittest.mock.Mock() + handler.emit(sample_record) + sample_message['timestamp'] = unittest.mock.ANY + handler.buffer_log.assert_called_once_with(sample_message) + + def try_request_test(self): + requests.post = unittest.mock.Mock() + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + requests.post.assert_called_with( + url=handler.url, + json={ + 'e': 'ls', + 'ls': handler.buf + }, + auth=('user', handler.key), + params={ + 'hostname': handler.hostname, + 'ip': handler.ip, + 'mac': handler.mac, + 'tags': handler.tags + }, + stream=True, + timeout=handler.request_timeout, + headers={'user-agent': handler.user_agent}) + + def close_test(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + handler.close_flusher = unittest.mock.Mock() + handler.flush_sync = unittest.mock.Mock() + handler.close() + handler.close_flusher.assert_called_once_with() + handler.flush_sync.assert_called_once_with() + self.assertIsNone(handler.worker_thread_pool) + self.assertIsNone(handler.request_thread_pool) + + def flush_test(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + handler.worker_thread_pool = MockThreadPoolExecutor() + handler.request_thread_pool = MockThreadPoolExecutor() + handler.buf_size += 1 + handler.try_request = unittest.mock.Mock() + handler.flush() + handler.try_request.assert_called_once_with() + + def buffer_log_test(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + handler.worker_thread_pool = MockThreadPoolExecutor() + handler.request_thread_pool = MockThreadPoolExecutor() + handler.close_flusher = unittest.mock.Mock() + handler.flush = unittest.mock.Mock() + sample_message['timestamp'] = unittest.mock.ANY + handler.flush_limit = 0 + handler.buffer_log(sample_message) + handler.close_flusher.assert_called_once_with() + handler.flush.assert_called_once_with() + self.assertEqual(handler.buf, [sample_message]) + self.assertEqual(handler.buf_size, len(sample_message['line'])) + def test_run_tests(self): self.handler_test() self.flusher_test() + self.emit_test() + self.try_request_test() + self.close_test() + self.flush_test() + self.buffer_log_test() if __name__ == '__main__': diff --git a/tests/test_utils.py b/tests/test_utils.py index 21c990b..446840f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,9 @@ import unittest from unittest.mock import patch -from logdna.utils import is_jsonable, sanitize_meta, get_ip +from logdna.utils import is_jsonable +from logdna.utils import sanitize_meta +from logdna.utils import get_ip +from logdna.utils import normalize_list_option IP = '10.0.50.10' VIP = '10.1.60.20' @@ -25,11 +28,11 @@ def setUp(self): self.invalid = {'bar': 'foo', 'baz': set()} def test_sanitize_simple(self): - clean = sanitize_meta(self.valid) + clean = sanitize_meta(self.valid, True) self.assertDictEqual(clean, self.valid) def test_sanitize_complex(self): - clean = sanitize_meta(self.invalid) + clean = sanitize_meta(self.invalid, True) self.assertDictEqual(clean, { 'bar': 'foo', '__errors': 'These keys have been sanitized: baz' @@ -46,3 +49,13 @@ def test_get_ip_socket_error(self, _): **{'return_value.getsockname.return_value': [IP, VIP]}) def test_get_ip_default(self, _): self.assertEqual(get_ip(), IP, 'default to localhost on error') + + +class NormalizeListOptionTest(unittest.TestCase): + def test_normalize_simple(self): + value1 = normalize_list_option({'tags': ' a, b'}, 'tags') + value2 = normalize_list_option({'tags': ['a', 'b']}, 'tags') + value3 = normalize_list_option({'tags': ('a', 'b')}, 'tags') + self.assertEqual(value1, ['a', 'b']) + self.assertEqual(value1, value2) + self.assertEqual(value3, []) From 5dd754f177675eb25cd5d5449bd3bcc8286f8739 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Thu, 15 Jul 2021 17:20:27 +0400 Subject: [PATCH 108/139] docs: update the README as requested Semver: patch --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ba9ab0f..5140281 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ * [Installation](#installation) * [Setup](#setup) * [Usage](#usage) - * [Usage with File Config](#usage-with-file-config) + * [Usage with fileConfig](#usage-with-fileconfig) * [API](#api) * [LogDNAHandler(key: string, [options: dict])](#logdnahandlerkey-string-options-dict) * [key](#key) @@ -70,7 +70,7 @@ _**Optional**_ * Hostname - ([string][]) * MAC Address - ([string][]) * IP Address - ([string][]) -* Index Meta - ([bool][]) - formatted as options['index_meta'] +* Index Meta - ([bool][]) - formatted as `options['index_meta']` ## Usage @@ -101,7 +101,7 @@ opts = { log.info('My Sample Log Line', opts) ``` -### Usage with File Config +### Usage with fileConfig To use [LogDNAHandler](#logdnahandlerkey-string-options-dict) with [fileConfig][] (e.g., in a Django `settings.py` file): @@ -116,7 +116,7 @@ LOGGING = { 'logdna': { 'level': logging.DEBUG, 'class': 'logging.handlers.LogDNAHandler', - 'key': os.environ.get('LOGDNA_INGEST_KEY'), + 'key': os.environ.get('LOGDNA_INGESTION_KEY'), 'options': { 'app': '', 'env': os.environ.get('ENVIRONMENT'), @@ -133,7 +133,7 @@ LOGGING = { } ``` -(This example assumes you have set environment variables for `ENVIRONMENT` and `LOGDNA_INGEST_KEY`) +(This example assumes you have set environment variables for `ENVIRONMENT` and `LOGDNA_INGESTION_KEY`.) ## API @@ -143,7 +143,7 @@ LOGGING = { * _**Required**_ * Type: [string][] -* Values: `YourAPIKey` +* Values: `` The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your account. @@ -154,16 +154,16 @@ The [LogDNA API Key](https://app.logdna.com/manage/profile) associated with your * _Optional_ * Type: [string][] * Default: `''` -* Values: `YourCustomApp` +* Values: `` -The default app passed along with every log sent through this instance. +The default app named that is included in every every log line sent through this instance. ##### env * _Optional_ * Type: [string][] * Default: `''` -* Values: `YourCustomEnv` +* Values: `` The default env passed along with every log sent through this instance. @@ -172,7 +172,7 @@ The default env passed along with every log sent through this instance. * _Optional_ * Type: [string][] * Default: `''` -* Values: `YourCustomHostname` +* Values: `` The default hostname passed along with every log sent through this instance. @@ -198,14 +198,14 @@ We allow meta objects to be passed with each line. By default these meta objects If this option is set to True then meta objects are parsed and searchable up to three levels deep. Any fields deeper than three levels are stringified and cannot be searched. -*WARNING* If this option is True, your metadata objects MUST have consistent types across all log messages or the metadata object may not be parsed properly. +*WARNING* If this option is True, your metadata objects MUST have consistent types across all log messages or the metadata object might not be parsed properly. ##### level * _Optional_ * Type: [string][] * Default: `Info` -* Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` +* Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `` The default level passed along with every log sent through this instance. @@ -267,7 +267,7 @@ The log line to be sent to LogDNA. * _Optional_ * Type: [string][] * Default: `Info` -* Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `YourCustomLevel` +* Values: `Debug`, `Trace`, `Info`, `Warn`, `Error`, `Fatal`, `` The level passed along with this log line. @@ -276,7 +276,7 @@ The level passed along with this log line. * _Optional_ * Type: [string][] * Default: `''` -* Values: `YourCustomApp` +* Values: `` The app passed along with this log line. @@ -285,7 +285,7 @@ The app passed along with this log line. * _Optional_ * Type: [string][] * Default: `''` -* Values: `YourCustomEnv` +* Values: `` The environment passed with this log line. From 946aa732e4d60de71e0357dda815a4e8bc852b70 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 15 Jul 2021 14:30:17 +0000 Subject: [PATCH 109/139] release: Version 1.17.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 8 ++++++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd813e..45b74ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ +## v1.17.0 (2021-07-15) +### Feature +* **meta:** Enable adding custom meta fields ([`f250237`](https://github.com/logdna/python/commit/f250237dbde932e99ab199023516f66a248c5e80)) +* **threadWorkerPools:** Introduce extra threads ([`9bfe479`](https://github.com/logdna/python/commit/9bfe479132acb0aa8e5784f2aa31298606e49789)) + +### Documentation +* Update the README as requested ([`5dd754f`](https://github.com/logdna/python/commit/5dd754f177675eb25cd5d5449bd3bcc8286f8739)) + ## v1.16.0 (2021-04-15) diff --git a/logdna/VERSION b/logdna/VERSION index 15b989e..092afa1 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.16.0 +1.17.0 diff --git a/pyproject.toml b/pyproject.toml index 3f4c0a5..f88222e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.16.0" +version = "1.17.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From e326e4c2461b808b5d3a885b37555f8e610615e4 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 26 Jul 2021 17:36:43 +0400 Subject: [PATCH 110/139] fix(opts): repair logging options for each call Semver: patch --- logdna/logdna.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/logdna/logdna.py b/logdna/logdna.py index 4a835e3..2ad02fc 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -238,6 +238,15 @@ def emit(self, record): message['meta'][key] = record[key] message['meta'] = sanitize_meta(message['meta'], self.index_meta) + + opts = {} + if 'args' in record and not isinstance(record['args'], tuple): + opts = record['args'] + + for key in ['app', 'env', 'hostname', 'level', 'timestamp']: + if key in opts: + message[key] = opts[key] + self.buffer_log(message) def close(self): From 6918c2dd9b0045b88539876016a5ac69aa97e8a8 Mon Sep 17 00:00:00 2001 From: Samir Musali Date: Mon, 26 Jul 2021 17:58:03 +0400 Subject: [PATCH 111/139] test(opts): test call-specific opts Semver: patch --- pyproject.toml | 2 +- tests/test_logger.py | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f88222e..ffc6f3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ branch = true source = ["logdna"] [tool.coverage.report] -fail_under = 75 +fail_under = 76 show_missing = true [tool.coverage.json] diff --git a/tests/test_logger.py b/tests/test_logger.py index 2c18b97..0c0b15e 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -10,16 +10,23 @@ LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' logger = logging.getLogger('logdna') logger.setLevel(logging.INFO) +sample_args = { + 'app': 'differentTest', + 'level': 'debug', + 'hostname': 'differentHost', + 'env': 'differentEnv' +} sample_record = logging.LogRecord('test', logging.INFO, 'test', 5, - 'Something to test', '', '', '', '') + 'Something to test', [sample_args], '', '', + '') sample_message = { 'line': 'Something to test', - 'hostname': 'localhost', - 'level': 'INFO', - 'app': 'test', - 'env': '', + 'hostname': 'differentHost', + 'level': 'debug', + 'app': 'differentTest', + 'env': 'differentEnv', 'meta': { - 'args': '', + 'args': sample_args, 'name': 'test', 'pathname': 'test', 'lineno': 5 From 398e82f6d46db00f895cc94612751e0c559c2aaa Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Mon, 26 Jul 2021 15:20:36 +0000 Subject: [PATCH 112/139] release: Version 1.18.0 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b74ae..d502474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.0 (2021-07-26) +### Fix +* **opts:** Repair logging options for each call ([`e326e4c`](https://github.com/logdna/python/commit/e326e4c2461b808b5d3a885b37555f8e610615e4)) + ## v1.17.0 (2021-07-15) ### Feature * **meta:** Enable adding custom meta fields ([`f250237`](https://github.com/logdna/python/commit/f250237dbde932e99ab199023516f66a248c5e80)) diff --git a/logdna/VERSION b/logdna/VERSION index 092afa1..84cc529 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.17.0 +1.18.0 diff --git a/pyproject.toml b/pyproject.toml index ffc6f3c..036a22e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.17.0" +version = "1.18.0" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From d00b9529d116ddf9ba454462d4d804dc54423c83 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Thu, 28 Oct 2021 15:30:49 -0500 Subject: [PATCH 113/139] fix(threading): account for secondary buffer flush and deadlock In the case that work cannot be scheduled on the worker pool, logs are pushed onto the `secondary` list and the check before a send doesn't account for any logs that may be pending in the `secondary`. This can happen in the case of a run time error when attempting to submit work to one of the thread pools effectively leaving logs unsent. Additionally eliminates a situation where lock wasn't being released during and error situation and would evenutally result in every tread being locked. This would prevent logs from being sent fixes: #74 --- Makefile | 12 ++++++++++-- logdna/logdna.py | 18 ++++++++++++------ tests/test_logger.py | 31 +++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 3e4f1b4..f2dd7ec 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,9 @@ debug-%: ## Debug a variable by calling `make debug-VARIABLE` help: ## Show this help, includes list of all actions. @awk 'BEGIN {FS = ":.*?## "}; /^.+: .*?## / && !/awk/ {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ${MAKEFILE_LIST} +.PHONY:run +run: ## purge build time artifacts + $(DOCKER_COMMAND) bash .PHONY:clean clean: ## purge build time artifacts rm -rf dist/ build/ coverage/ pypoetry/ pip/ **/__pycache__/ .pytest_cache/ .cache .coverage @@ -63,11 +66,16 @@ package: ## Generate a python sdist and wheel $(POETRY_COMMAND) build .PHONY:release -release: clean install ## run semantic release build and publish results to github + pypi based on unreleased commits +release: clean install fetch-tags ## run semantic release build and publish results to github + pypi based on unreleased commits $(POETRY_COMMAND) run task release +.PHONY: fetch-tags +fetch-tags: ## workaround for jenkins repo cloning behavior + git config remote.origin.url "https://logdnabot:${GH_TOKEN}@github.com/logdna/python" + git fetch origin --tags + .PHONY:release-dry -release-dry: clean install changelog ## run semantic release in noop mode +release-dry: clean install fetch-tags changelog ## run semantic release in noop mode $(POETRY_COMMAND) run semantic-release publish --noop --verbosity=DEBUG .PHONY:release-patch diff --git a/logdna/logdna.py b/logdna/logdna.py index 2ad02fc..412ae7a 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -109,21 +109,21 @@ def buffer_log_sync(self, message): self.internalLogger.debug( 'The buffer size exceeded the limit: %s', self.buf_retention_limit) - self.lock.release() if self.buf_size >= self.flush_limit and not self.exception_flag: self.close_flusher() self.flush() else: self.start_flusher() + self.lock.release() else: self.secondary.append(message) def clean_after_success(self): - del self.buf[:] + self.close_flusher() + self.buf.clear() self.buf_size = 0 self.exception_flag = False - self.close_flusher() def flush(self): if self.worker_thread_pool: @@ -135,8 +135,9 @@ def flush(self): self.internalLogger.debug('Error in calling flush: %s', e) def flush_sync(self): - if self.buf_size == 0: + if self.buf_size == 0 and len(self.secondary) == 0: return + if self.lock.acquire(blocking=False): if self.request_thread_pool: try: @@ -146,8 +147,12 @@ def flush_sync(self): except Exception as e: self.internalLogger.debug( 'Error in calling try_request: %s', e) - self.lock.release() + finally: + self.lock.release() + else: + self.lock.release() else: + self.close_flusher() self.start_flusher() def try_request(self): @@ -181,7 +186,8 @@ def send_request(self, data): 'hostname': self.hostname, 'ip': self.ip, 'mac': self.mac, - 'tags': self.tags + 'tags': self.tags, + 'now': int(time.time() * 1000) }, stream=True, timeout=self.request_timeout, diff --git a/tests/test_logger.py b/tests/test_logger.py index 0c0b15e..91e45e0 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,11 +1,14 @@ import logging import unittest import requests +import time from logdna import LogDNAHandler from concurrent.futures import ThreadPoolExecutor from logdna.configs import defaults +from unittest import mock +now = int(time.time()) expectedLines = [] LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' logger = logging.getLogger('logdna') @@ -37,7 +40,8 @@ 'ip': '10.0.1.1', 'mac': 'C0:FF:EE:C0:FF:EE', 'tags': 'sample,test', - 'index_meta': True + 'index_meta': True, + 'now': int(time.time() * 1000) } @@ -124,6 +128,7 @@ def emit_test(self): sample_message['timestamp'] = unittest.mock.ANY handler.buffer_log.assert_called_once_with(sample_message) + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def try_request_test(self): requests.post = unittest.mock.Mock() handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) @@ -141,7 +146,8 @@ def try_request_test(self): 'hostname': handler.hostname, 'ip': handler.ip, 'mac': handler.mac, - 'tags': handler.tags + 'tags': handler.tags, + 'now': int(now * 1000) }, stream=True, timeout=handler.request_timeout, @@ -166,6 +172,26 @@ def flush_test(self): handler.flush() handler.try_request.assert_called_once_with() + def flush_secondary_test(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + clean = handler.clean_after_success + handler.worker_thread_pool = MockThreadPoolExecutor() + handler.request_thread_pool = MockThreadPoolExecutor() + handler.buf_size = 0 + handler.secondary.append(sample_message) + handler.send_request = unittest.mock.MagicMock(return_value=True) + handler.clean_after_success = unittest.mock.Mock() + self.assertEqual(handler.secondary, [sample_message]) + handler.flush() + handler.send_request.assert_called_with({ + 'e': 'ls', + 'ls': [sample_message] + }) + + clean() + self.assertEqual(handler.secondary, []) + self.assertEqual(handler.buf, []) + def buffer_log_test(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.worker_thread_pool = MockThreadPoolExecutor() @@ -187,6 +213,7 @@ def test_run_tests(self): self.try_request_test() self.close_test() self.flush_test() + self.flush_secondary_test() self.buffer_log_test() From 6d5d432f5b9cf1083c54e7c74125d884c7674f9d Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 18 Nov 2021 22:40:26 +0000 Subject: [PATCH 114/139] release: Version 1.18.1 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d502474..0d33be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.1 (2021-11-18) +### Fix +* **threading:** Account for secondary buffer flush and deadlock ([`d00b952`](https://github.com/logdna/python/commit/d00b9529d116ddf9ba454462d4d804dc54423c83)) + ## v1.18.0 (2021-07-26) ### Fix * **opts:** Repair logging options for each call ([`e326e4c`](https://github.com/logdna/python/commit/e326e4c2461b808b5d3a885b37555f8e610615e4)) diff --git a/logdna/VERSION b/logdna/VERSION index 84cc529..ec6d649 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.0 +1.18.1 diff --git a/pyproject.toml b/pyproject.toml index 036a22e..02ba191 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.0" +version = "1.18.1" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From e4128592aa9c2301c6467115148f2f23f88da9d3 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Tue, 10 May 2022 08:55:13 -0700 Subject: [PATCH 115/139] fix: requests error handling (#82) - added locally built docker build-image - fixed retries for 5xx errors - fixed various leftover build issues LOG-11814 --- Dockerfile | 7 + Jenkinsfile | 19 +- Makefile | 29 +- logdna/logdna.py | 86 ++++- poetry.lock | 863 ++++++++++++++++++++++++------------------- pyproject.toml | 6 +- tests/test_logger.py | 120 +++--- 7 files changed, 653 insertions(+), 477 deletions(-) create mode 100644 Dockerfile mode change 100644 => 100755 Jenkinsfile mode change 100644 => 100755 Makefile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..db887bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM condaforge/miniforge3:4.12.0-0 + +RUN conda install -y gcc pip poetry=1.1.7 git +RUN mkdir /workdir && chmod 777 /workdir +RUN git config --global --add safe.directory /workdir +WORKDIR /workdir + diff --git a/Jenkinsfile b/Jenkinsfile old mode 100644 new mode 100755 index 009701d..f4f00ee --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,12 @@ def DEFAULT_BRANCH = 'master' def CURRENT_BRANCH = [env.CHANGE_BRANCH, env.BRANCH_NAME]?.find{branch -> branch != null} pipeline { - agent none + agent { + node { + label 'ec2-fleet' + customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" + } + } options { timestamps() @@ -18,12 +23,6 @@ pipeline { stages { stage('Test') { - agent { - node { - label 'ec2-fleet' - customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" - } - } steps { sh 'make install lint test' @@ -45,12 +44,6 @@ pipeline { } stage('Release') { - agent { - node { - label 'ec2-fleet' - customWorkspace "${PROJECT_NAME}-${BUILD_NUMBER}" - } - } stages { stage('dry run') { diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index f2dd7ec..a7f8886 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ include .config.mk DOCKER = docker DOCKER_RUN := $(DOCKER) run --rm -i WORKDIR :=/workdir -DOCKER_COMMAND := $(DOCKER_RUN) -v $(PWD):$(WORKDIR):Z -w $(WORKDIR) \ +DOCKER_COMMAND := $(DOCKER_RUN) -u "$(shell id -u)":"$(shell id -g)" -v $(PWD):$(WORKDIR):Z -w $(WORKDIR) \ -e XDG_CONFIG_HOME=$(WORKDIR) \ -e XDG_CACHE_HOME=$(WORKDIR) \ -e POETRY_CACHE_DIR=$(WORKDIR)/.cache \ @@ -21,13 +21,19 @@ DOCKER_COMMAND := $(DOCKER_RUN) -v $(PWD):$(WORKDIR):Z -w $(WORKDIR) \ -e GIT_AUTHOR_EMAIL \ -e GIT_COMMITTER_NAME \ -e GIT_COMMITTER_EMAIL \ - us.gcr.io/logdna-k8s/python:3.7-ci + logdna-poetry:local + POETRY_COMMAND := $(DOCKER_COMMAND) poetry # Exports the variables for shell use export +# build image +.PHONY:build-image +build-image: + DOCKER_BUILDKIT=1 $(DOCKER) build -t logdna-poetry:local . + # This helper function makes debugging much easier. .PHONY:debug-% debug-%: ## Debug a variable by calling `make debug-VARIABLE` @@ -39,30 +45,31 @@ help: ## Show this help, includes list of all actions. @awk 'BEGIN {FS = ":.*?## "}; /^.+: .*?## / && !/awk/ {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ${MAKEFILE_LIST} .PHONY:run -run: ## purge build time artifacts +run: install ## purge build time artifacts $(DOCKER_COMMAND) bash + .PHONY:clean clean: ## purge build time artifacts rm -rf dist/ build/ coverage/ pypoetry/ pip/ **/__pycache__/ .pytest_cache/ .cache .coverage .PHONY:changelog -changelog: ## print the next version of the change log to stdout +changelog: install ## print the next version of the change log to stdout $(POETRY_COMMAND) run semantic-release changelog --unreleased .PHONY:install -install: ## install development and build time dependencies - $(POETRY_COMMAND) install --no-interaction -vvv +install: build-image ## install development and build time dependencies + $(POETRY_COMMAND) install --no-interaction .PHONY:lint -lint: ## run lint rules and print error report +lint: install ## run lint rules and print error report $(POETRY_COMMAND) run task lint .PHONY:lint-fix -lint-fix:## attempt to auto fix linting error and report remaining errors +lint-fix: install ## attempt to auto fix linting error and report remaining errors $(POETRY_COMMAND) run task lint:fix .PHONY:package -package: ## Generate a python sdist and wheel +package: install ## Generate a python sdist and wheel $(POETRY_COMMAND) build .PHONY:release @@ -71,7 +78,6 @@ release: clean install fetch-tags ## run semantic release build and publish resu .PHONY: fetch-tags fetch-tags: ## workaround for jenkins repo cloning behavior - git config remote.origin.url "https://logdnabot:${GH_TOKEN}@github.com/logdna/python" git fetch origin --tags .PHONY:release-dry @@ -91,6 +97,5 @@ release-major: clean install ## run semantic release build an $(POETRY_COMMAND) run semantic-release publish --major .PHONY:test -test: ## run project test suite +test: install ## run project test suite $(POETRY_COMMAND) run task test - diff --git a/logdna/logdna.py b/logdna/logdna.py index 412ae7a..ec20841 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -177,7 +177,13 @@ def try_request(self): self.close_flusher() self.exception_flag = True - def send_request(self, data): + def send_request(self, data): # noqa: max-complexity: 13 + """ + Send log data to LogDNA server + Returns: + True - discard flush buffer + False - retry, keep flush buffer + """ try: response = requests.post(url=self.url, json=data, @@ -190,38 +196,84 @@ def send_request(self, data): 'now': int(time.time() * 1000) }, stream=True, + allow_redirects=True, timeout=self.request_timeout, headers={'user-agent': self.user_agent}) - response.raise_for_status() status_code = response.status_code - if status_code in [401, 403]: - self.internalLogger.debug( - 'Please provide a valid ingestion key.' + - ' Discarding flush buffer') - return True - + ''' + response code: + 1XX unexpected status + 200 expected status, OK + 2XX unexpected status + 301 302 303 unexpected status, + per "allow_redirects=True" + 3XX unexpected status + 401, 403 expected client error, + invalid ingestion key + 4XX unexpected client error + 500 502 503 507 expected server error, transient + 5XX unexpected server error + handling: + expected status discard flush buffer + unexpected status log + discard flush buffer + expected client error log + discard flush buffer + unexpected client error log + discard flush buffer + expected server error log + retry + unexpected server error log + discard flush buffer + ''' if status_code == 200: - return True + return True # discard - if status_code in [400, 500, 504]: - self.internalLogger.debug('The request failed %s. Retrying...', - response.reason) - return True + if isinstance(response.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = response.reason.decode('utf-8') + except UnicodeDecodeError: + reason = response.reason.decode('iso-8859-1') else: + reason = response.reason + + if 200 < status_code <= 399: + self.internalLogger.debug('Unexpected response: %s. ' + + 'Discarding flush buffer', + reason) + return True # discard + + if status_code in [401, 403]: self.internalLogger.debug( - 'The request failed: %s. Retrying...', response.reason) + 'Please provide a valid ingestion key. ' + + 'Discarding flush buffer') + return True # discard + + if 400 <= status_code <= 499: + self.internalLogger.debug('Client Error: %s. ' + + 'Discarding flush buffer', + reason) + return True # discard + + if status_code in [500, 502, 503, 507]: + self.internalLogger.debug('Server Error: %s. Retrying...', + reason) + return False # retry + + self.internalLogger.debug('The request failed: %s.' + + 'Discarding flush buffer', + reason) except requests.exceptions.Timeout as timeout: - self.internalLogger.debug('Timeout error occurred %s. Retrying...', + self.internalLogger.debug('Timeout Error: %s. Retrying...', timeout) + return False # retry except requests.exceptions.RequestException as exception: self.internalLogger.debug( 'Error sending logs %s. Discarding flush buffer', exception) - return True - return False + return True # discard def emit(self, record): msg = self.format(record) diff --git a/poetry.lock b/poetry.lock index 8490cff..ae55ed5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "appnope" -version = "0.1.2" +version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" category = "dev" optional = false @@ -16,17 +16,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "backcall" @@ -38,20 +38,23 @@ python-versions = "*" [[package]] name = "bleach" -version = "3.3.0" +version = "5.0.0" description = "An easy safelist-based HTML-sanitizing tool." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" [package.dependencies] -packaging = "*" six = ">=1.9.0" webencodings = "*" +[package.extras] +css = ["tinycss2 (>=1.1.0)"] +dev = ["pip-tools (==6.5.1)", "pytest (==7.1.1)", "flake8 (==4.0.1)", "tox (==3.24.5)", "sphinx (==4.3.2)", "twine (==4.0.0)", "wheel (==0.37.1)", "hashin (==0.17.0)", "black (==22.3.0)", "mypy (==0.942)"] + [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -59,7 +62,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.5" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "dev" optional = false @@ -69,24 +72,31 @@ python-versions = "*" pycparser = "*" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "7.1.2" +version = "8.1.3" description = "Composable command line interface toolkit" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "click-log" -version = "0.3.2" +version = "0.4.0" description = "Logging integration for Click" category = "dev" optional = false @@ -105,7 +115,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "5.4" +version = "5.5" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -116,7 +126,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "3.4.7" +version = "37.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "dev" optional = false @@ -127,23 +137,23 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "decorator" -version = "4.4.2" +version = "5.1.1" description = "Decorators for Humans" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" +python-versions = ">=3.5" [[package]] name = "docutils" -version = "0.17" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -162,63 +172,65 @@ setuptools_scm = "*" [[package]] name = "flake8" -version = "3.8.4" +version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "gitdb" -version = "4.0.7" +version = "4.0.9" description = "Git Object Database" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.6" [package.dependencies] -smmap = ">=3.0.1,<5" +smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.14" -description = "Python Git Library" +version = "3.1.27" +description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.7" [package.dependencies] gitdb = ">=4.0.1,<5" +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "3.4.0" +version = "4.11.3" description = "Read metadata from Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -230,7 +242,7 @@ python-versions = "*" [[package]] name = "invoke" -version = "1.5.0" +version = "1.7.0" description = "Pythonic task execution" category = "dev" optional = false @@ -238,37 +250,40 @@ python-versions = "*" [[package]] name = "ipdb" -version = "0.13.4" +version = "0.13.9" description = "IPython-enabled pdb" category = "dev" optional = false python-versions = ">=2.7" [package.dependencies] -ipython = {version = ">=5.1.0", markers = "python_version >= \"3.4\""} +decorator = {version = "*", markers = "python_version > \"3.6\""} +ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""} +toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""} [[package]] name = "ipython" -version = "7.16.1" +version = "7.33.0" description = "IPython: Productive Interactive Computing" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" -jedi = ">=0.10" -pexpect = {version = "*", markers = "sys_platform != \"win32\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" traitlets = ">=4.2" [package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] @@ -276,19 +291,11 @@ nbformat = ["nbformat"] notebook = ["notebook", "ipywidgets"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] - -[[package]] -name = "ipython-genutils" -version = "0.2.0" -description = "Vestigial utilities from IPython" -category = "dev" -optional = false -python-versions = "*" +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] [[package]] name = "jedi" -version = "0.18.0" +version = "0.18.1" description = "An autocompletion tool for Python that can be used for text editors." category = "dev" optional = false @@ -299,36 +306,48 @@ parso = ">=0.8.0,<0.9.0" [package.extras] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jeepney" -version = "0.6.0" +version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] +test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +trio = ["trio", "async-generator"] [[package]] name = "keyring" -version = "22.3.0" +version = "23.5.0" description = "Store and access your passwords safely." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -importlib-metadata = {version = ">=1", markers = "python_version < \"3.8\""} +importlib-metadata = ">=3.6" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.3" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +traitlets = "*" [[package]] name = "mccabe" @@ -348,18 +367,18 @@ python-versions = ">=3.5" [[package]] name = "packaging" -version = "20.9" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "parso" -version = "0.8.1" +version = "0.8.3" description = "A Python Parser" category = "dev" optional = false @@ -390,43 +409,44 @@ python-versions = "*" [[package]] name = "pkginfo" -version = "1.7.0" +version = "1.8.2" description = "Query metadatdata from sdists / bdists / installed packages." category = "dev" optional = false python-versions = "*" [package.extras] -testing = ["nose", "coverage"] +testing = ["coverage", "nose"] [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.3" +version = "3.0.29" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] wcwidth = "*" [[package]] name = "psutil" -version = "5.8.0" +version = "5.9.0" description = "Cross-platform lib for process and system monitoring in Python." category = "dev" optional = false @@ -445,15 +465,15 @@ python-versions = "*" [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false @@ -461,7 +481,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pycparser" -version = "2.20" +version = "2.21" description = "C parser in Python" category = "dev" optional = false @@ -469,7 +489,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" -version = "2.2.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -477,23 +497,26 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.7.4" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" -version = "6.2.2" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -506,7 +529,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -515,7 +538,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.11.1" +version = "2.12.1" description = "Pytest plugin for measuring coverage." category = "dev" optional = false @@ -524,50 +547,52 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] coverage = ">=5.2.1" pytest = ">=4.6" +toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "python-gitlab" -version = "2.6.0" +version = "3.4.0" description = "Interact with GitLab API" category = "dev" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" [package.dependencies] -requests = ">=2.22.0" +requests = ">=2.25.0" requests-toolbelt = ">=0.9.1" [package.extras] -autocompletion = ["argcomplete (>=1.10.0,<2)"] +autocompletion = ["argcomplete (>=1.10.0,<3)"] yaml = ["PyYaml (>=5.2)"] [[package]] name = "python-semantic-release" -version = "7.15.3" +version = "7.28.1" description = "Automatic Semantic Versioning for Python projects" category = "dev" optional = false python-versions = "*" [package.dependencies] -click = ">=7,<8" +click = ">=7,<9" click-log = ">=0.3,<1" dotty-dict = ">=1.3.0,<2" gitpython = ">=3.0.8,<4" invoke = ">=1.4.1,<2" -python-gitlab = ">=1.10,<3" +python-gitlab = ">=2,<4" requests = ">=2.25,<3" semver = ">=2.10,<3" -tomlkit = ">=0.7.0,<1.0.0" +tomlkit = ">=0.10.0,<0.11.0" twine = ">=3,<4" [package.extras] -dev = ["mypy", "tox", "isort", "black"] +dev = ["tox", "isort", "black"] docs = ["Sphinx (==1.3.6)"] -test = ["coverage (>=5,<6)", "pytest (>=5,<6)", "pytest-xdist (>=1,<2)", "pytest-mock (>=2,<3)", "responses (==0.5.0)", "mock (==1.3.0)"] +mypy = ["mypy", "types-requests"] +test = ["coverage (>=5,<6)", "pytest (>=5,<6)", "pytest-xdist (>=1,<2)", "pytest-mock (>=2,<3)", "responses (==0.13.3)", "mock (==1.3.0)"] [[package]] name = "pywin32-ctypes" @@ -579,38 +604,37 @@ python-versions = "*" [[package]] name = "readme-renderer" -version = "29.0" +version = "35.0" description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] bleach = ">=2.1.0" docutils = ">=0.13.1" Pygments = ">=2.5.1" -six = "*" [package.extras] -md = ["cmarkgfm (>=0.5.0,<0.6.0)"] +md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "requests-toolbelt" @@ -623,20 +647,35 @@ python-versions = "*" [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "responses" +version = "0.20.0" +description = "A utility library for mocking out the `requests` Python library." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +requests = ">=2.0,<3.0" +urllib3 = ">=1.25.10" + +[package.extras] +tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-localserver", "flake8", "types-mock", "types-requests", "mypy"] + [[package]] name = "rfc3986" -version = "1.4.0" +version = "2.0.0" description = "Validating URI References per RFC 3986" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.extras] idna2008 = ["idna"] [[package]] name = "secretstorage" -version = "3.3.1" +version = "3.3.2" description = "Python bindings to FreeDesktop.org Secret Service API" category = "dev" optional = false @@ -656,18 +695,23 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "setuptools-scm" -version = "6.0.1" +version = "6.4.2" description = "the blessed package to manage your versions by scm tags" category = "dev" optional = false python-versions = ">=3.6" +[package.dependencies] +packaging = ">=20.0" +tomli = ">=1.0.0" + [package.extras] -toml = ["toml"] +test = ["pytest (>=6.2)", "virtualenv (>20)"] +toml = ["setuptools (>=42)"] [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false @@ -675,15 +719,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "smmap" -version = "4.0.0" +version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "tap.py" -version = "3.0" +version = "3.1" description = "Test Anything Protocol (TAP) tools" category = "dev" optional = false @@ -694,16 +738,17 @@ yaml = ["more-itertools", "PyYAML (>=5.1)"] [[package]] name = "taskipy" -version = "1.6.0" +version = "1.10.1" description = "tasks runner for python projects" category = "dev" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -mslex = ">=0.3.0,<0.4.0" +colorama = ">=0.4.4,<0.5.0" +mslex = {version = ">=0.3.0,<0.4.0", markers = "sys_platform == \"win32\""} psutil = ">=5.7.2,<6.0.0" -toml = ">=0.10.0,<0.11.0" +tomli = ">=1.2.3,<2.0.0" [[package]] name = "toml" @@ -713,46 +758,53 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "tomlkit" -version = "0.7.0" +version = "0.10.2" description = "Style preserving TOML library" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6,<4.0" [[package]] name = "tqdm" -version = "4.59.0" +version = "4.64.0" description = "Fast, Extensible Progress Meter" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [package.extras] dev = ["py-make (>=0.1.0)", "twine", "wheel"] notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] telegram = ["requests"] [[package]] name = "traitlets" -version = "4.3.3" -description = "Traitlets Python config system" +version = "5.1.1" +description = "Traitlets Python configuration system" category = "dev" optional = false -python-versions = "*" - -[package.dependencies] -decorator = "*" -ipython-genutils = "*" -six = "*" +python-versions = ">=3.7" [package.extras] -test = ["pytest", "mock"] +test = ["pytest"] [[package]] name = "twine" -version = "3.3.0" +version = "3.8.0" description = "Collection of utilities for publishing packages on PyPI" category = "dev" optional = false @@ -760,33 +812,34 @@ python-versions = ">=3.6" [package.dependencies] colorama = ">=0.4.3" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-metadata = ">=3.6" keyring = ">=15.1" -pkginfo = ">=1.4.2" +pkginfo = ">=1.8.1" readme-renderer = ">=21.0" requests = ">=2.20" requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" rfc3986 = ">=1.4.0" tqdm = ">=4.14" +urllib3 = ">=1.26.0" [[package]] name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.3" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -816,228 +869,253 @@ python-versions = "*" [[package]] name = "zipp" -version = "3.4.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" -python-versions = "^3.6" -content-hash = "e32b90a7d407970b1a0211de99d4adda266a46f7e222c8d3bb4dd57dbbec17ca" +python-versions = "^3.7" +content-hash = "95a17f1fcf4d2a76cbd338051e679f32f1327891061b7e486b0f5f8ebb759532" [metadata.files] appnope = [ - {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, - {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] bleach = [ - {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"}, - {file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"}, + {file = "bleach-5.0.0-py3-none-any.whl", hash = "sha256:08a1fe86d253b5c88c92cc3d810fd8048a16d15762e1e5b74d502256e5926aa1"}, + {file = "bleach-5.0.0.tar.gz", hash = "sha256:c6d6cc054bdc9c83b48b8083e236e5f00f238428666d2ce2e083eaa5fd568565"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ - {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, - {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, - {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, - {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, - {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, - {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, - {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, - {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, - {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, - {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, - {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, - {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, - {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, - {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, - {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, - {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, - {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, - {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, - {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] click-log = [ - {file = "click-log-0.3.2.tar.gz", hash = "sha256:16fd1ca3fc6b16c98cea63acf1ab474ea8e676849dc669d86afafb0ed7003124"}, - {file = "click_log-0.3.2-py2.py3-none-any.whl", hash = "sha256:eee14dc37cdf3072158570f00406572f9e03e414accdccfccd4c538df9ae322c"}, + {file = "click-log-0.4.0.tar.gz", hash = "sha256:3970f8570ac54491237bcdb3d8ab5e3eef6c057df29f8c3d1151a51a9c23b975"}, + {file = "click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"}, - {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"}, - {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"}, - {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"}, - {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"}, - {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"}, - {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"}, - {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"}, - {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"}, - {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"}, - {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"}, - {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"}, - {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"}, - {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"}, - {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"}, - {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"}, - {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"}, - {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"}, - {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"}, - {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"}, - {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"}, - {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"}, - {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"}, - {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"}, - {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"}, - {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"}, - {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"}, - {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"}, - {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"}, - {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"}, - {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"}, - {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"}, - {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"}, - {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"}, - {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"}, - {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"}, - {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"}, - {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"}, - {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"}, - {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"}, - {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"}, - {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"}, - {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"}, - {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"}, - {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"}, - {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"}, - {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"}, - {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"}, - {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"}, + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] cryptography = [ - {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, - {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, - {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, - {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, - {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, - {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, - {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, - {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, - {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, - {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, - {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, - {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, + {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, + {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, + {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, ] decorator = [ - {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, - {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] docutils = [ - {file = "docutils-0.17-py2.py3-none-any.whl", hash = "sha256:a71042bb7207c03d5647f280427f14bfbd1a65c9eb84f4b341d85fafb6bb4bdf"}, - {file = "docutils-0.17.tar.gz", hash = "sha256:e2ffeea817964356ba4470efba7c2f42b6b0de0b04e66378507e3e2504bbff4c"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] dotty-dict = [ {file = "dotty_dict-1.3.0.tar.gz", hash = "sha256:eb0035a3629ecd84397a68f1f42f1e94abd1c34577a19cd3eacad331ee7cbaf0"}, ] flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] gitdb = [ - {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, - {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.14-py3-none-any.whl", hash = "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b"}, - {file = "GitPython-3.1.14.tar.gz", hash = "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, - {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, + {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, + {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] invoke = [ - {file = "invoke-1.5.0-py2-none-any.whl", hash = "sha256:da7c2d0be71be83ffd6337e078ef9643f41240024d6b2659e7b46e0b251e339f"}, - {file = "invoke-1.5.0-py3-none-any.whl", hash = "sha256:7e44d98a7dc00c91c79bac9e3007276965d2c96884b3c22077a9f04042bd6d90"}, - {file = "invoke-1.5.0.tar.gz", hash = "sha256:f0c560075b5fb29ba14dad44a7185514e94970d1b9d57dcd3723bec5fed92650"}, + {file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"}, + {file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"}, ] ipdb = [ - {file = "ipdb-0.13.4.tar.gz", hash = "sha256:c85398b5fb82f82399fc38c44fe3532c0dde1754abee727d8f5cfcc74547b334"}, + {file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"}, ] ipython = [ - {file = "ipython-7.16.1-py3-none-any.whl", hash = "sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64"}, - {file = "ipython-7.16.1.tar.gz", hash = "sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"}, -] -ipython-genutils = [ - {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, - {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, + {file = "ipython-7.33.0-py3-none-any.whl", hash = "sha256:916a3126896e4fd78dd4d9cf3e21586e7fd93bae3f1cd751588b75524b64bf94"}, + {file = "ipython-7.33.0.tar.gz", hash = "sha256:bcffb865a83b081620301ba0ec4d95084454f26b91d6d66b475bff3dfb0218d4"}, ] jedi = [ - {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, - {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, + {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, + {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, ] jeepney = [ - {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, - {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, ] keyring = [ - {file = "keyring-22.3.0-py3-none-any.whl", hash = "sha256:2bc8363ebdd63886126a012057a85c8cb6e143877afa02619ac7dbc9f38a207b"}, - {file = "keyring-22.3.0.tar.gz", hash = "sha256:16927a444b2c73f983520a48dec79ddab49fe76429ea05b8d528d778c8339522"}, + {file = "keyring-23.5.0-py3-none-any.whl", hash = "sha256:b0d28928ac3ec8e42ef4cc227822647a19f1d544f21f96457965dc01cf555261"}, + {file = "keyring-23.5.0.tar.gz", hash = "sha256:9012508e141a80bd1c0b6778d5c610dd9f8c464d75ac6774248500503f972fb9"}, +] +matplotlib-inline = [ + {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, + {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1048,12 +1126,12 @@ mslex = [ {file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] parso = [ - {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, - {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, @@ -1064,167 +1142,178 @@ pickleshare = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] pkginfo = [ - {file = "pkginfo-1.7.0-py2.py3-none-any.whl", hash = "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"}, - {file = "pkginfo-1.7.0.tar.gz", hash = "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4"}, + {file = "pkginfo-1.8.2-py2.py3-none-any.whl", hash = "sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc"}, + {file = "pkginfo-1.8.2.tar.gz", hash = "sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, - {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, + {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, + {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, ] psutil = [ - {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, - {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"}, - {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"}, - {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"}, - {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"}, - {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"}, - {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"}, - {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"}, - {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"}, - {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"}, - {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"}, - {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"}, - {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"}, - {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"}, - {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"}, - {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"}, - {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"}, - {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"}, - {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"}, - {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"}, - {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"}, - {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"}, - {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"}, - {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"}, - {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"}, - {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"}, - {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"}, - {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"}, + {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, + {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, + {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"}, + {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"}, + {file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"}, + {file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"}, + {file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"}, + {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"}, + {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"}, + {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"}, + {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"}, + {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"}, + {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"}, + {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"}, + {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"}, + {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"}, + {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"}, + {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"}, + {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"}, + {file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"}, + {file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"}, + {file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"}, + {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"}, + {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"}, + {file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"}, + {file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"}, + {file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"}, + {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"}, + {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"}, + {file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"}, + {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, + {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"}, - {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pytest = [ - {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, - {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ - {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, - {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] python-gitlab = [ - {file = "python-gitlab-2.6.0.tar.gz", hash = "sha256:a862c6874524ab585b725a17b2cd2950fc09d6d74205f40a11be2a4e8f2dcaa1"}, - {file = "python_gitlab-2.6.0-py3-none-any.whl", hash = "sha256:8f6a62b2804021416f13e91f359085f1071fb7adedfb292ef0d0877df777a075"}, + {file = "python-gitlab-3.4.0.tar.gz", hash = "sha256:6180b81ee2f265ad8d8412956a1740b4d3ceca7b28ae2f707dfe62375fed0082"}, + {file = "python_gitlab-3.4.0-py3-none-any.whl", hash = "sha256:251b63f0589d51f854516948c84e9eb8df26e1e9dea595cf86b43f17c43007dd"}, ] python-semantic-release = [ - {file = "python-semantic-release-7.15.3.tar.gz", hash = "sha256:0ffcb267923731b3578d4377de67c59c5779c80e514ef9883c47c8877c059d74"}, - {file = "python_semantic_release-7.15.3-py3-none-any.whl", hash = "sha256:9b5c6e7ff9887b0cc8178004aa101aae2bfec8a464316cf287be8acbdf1e4954"}, + {file = "python-semantic-release-7.28.1.tar.gz", hash = "sha256:d7f82b3d4c06b304d07689b8a1c7d0d448ff07c2ab81cd810c4f2f900f24c599"}, + {file = "python_semantic_release-7.28.1-py3-none-any.whl", hash = "sha256:319c3e811d6e10bc3f0e967419eae0e5e9ba1e6745aa2fd94dd24e3995541ca2"}, ] pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] readme-renderer = [ - {file = "readme_renderer-29.0-py2.py3-none-any.whl", hash = "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c"}, - {file = "readme_renderer-29.0.tar.gz", hash = "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"}, + {file = "readme_renderer-35.0-py3-none-any.whl", hash = "sha256:73b84905d091c31f36e50b4ae05ae2acead661f6a09a9abb4df7d2ddcdb6a698"}, + {file = "readme_renderer-35.0.tar.gz", hash = "sha256:a727999acfc222fc21d82a12ed48c957c4989785e5865807c65a487d21677497"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] +responses = [ + {file = "responses-0.20.0-py3-none-any.whl", hash = "sha256:18831bc2d72443b67664d98038374a6fa1f27eaaff4dd9a7d7613723416fea3c"}, + {file = "responses-0.20.0.tar.gz", hash = "sha256:644905bc4fb8a18fa37e3882b2ac05e610fe8c2f967d327eed669e314d94a541"}, +] rfc3986 = [ - {file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"}, - {file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"}, + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, ] secretstorage = [ - {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, - {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, + {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, + {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] setuptools-scm = [ - {file = "setuptools_scm-6.0.1-py3-none-any.whl", hash = "sha256:c3bd5f701c8def44a5c0bfe8d407bef3f80342217ef3492b951f3777bd2d915c"}, - {file = "setuptools_scm-6.0.1.tar.gz", hash = "sha256:d1925a69cb07e9b29416a275b9fadb009a23c148ace905b2fb220649a6c18e92"}, + {file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"}, + {file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] smmap = [ - {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, - {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] "tap.py" = [ - {file = "tap.py-3.0-py2.py3-none-any.whl", hash = "sha256:a598bfaa2e224d71f2e86147c2ef822c18ff2e1b8ef006397e5056b08f92f699"}, - {file = "tap.py-3.0.tar.gz", hash = "sha256:f5eeeeebfd64e53d32661752bb4c288589a3babbb96db3f391a4ec29f1359c70"}, + {file = "tap.py-3.1-py3-none-any.whl", hash = "sha256:928c852f3361707b796c93730cc5402c6378660b161114461066acf53d65bf5d"}, + {file = "tap.py-3.1.tar.gz", hash = "sha256:3c0cd45212ad5a25b35445964e2517efa000a118a1bfc3437dae828892eaf1e1"}, ] taskipy = [ - {file = "taskipy-1.6.0-py3-none-any.whl", hash = "sha256:33ee1d52b378cb4af3678fc459b75c3028f594c5e8e42ac0696cbe3e95d47394"}, - {file = "taskipy-1.6.0.tar.gz", hash = "sha256:ec4d1f2208ae24218950e3a2812e4e8b4397b1f65a6ad7e2b1240b702042fa3e"}, + {file = "taskipy-1.10.1-py3-none-any.whl", hash = "sha256:9b38333654da487b6d16de6fa330b7629d1935d1e74819ba4c5f17a1c372d37b"}, + {file = "taskipy-1.10.1.tar.gz", hash = "sha256:6fa0b11c43d103e376063e90be31d87b435aad50fb7dc1c9a2de9b60a85015ed"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] tomlkit = [ - {file = "tomlkit-0.7.0-py2.py3-none-any.whl", hash = "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831"}, - {file = "tomlkit-0.7.0.tar.gz", hash = "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"}, + {file = "tomlkit-0.10.2-py3-none-any.whl", hash = "sha256:905cf92c2111ef80d355708f47ac24ad1b6fc2adc5107455940088c9bbecaedb"}, + {file = "tomlkit-0.10.2.tar.gz", hash = "sha256:30d54c0b914e595f3d10a87888599eab5321a2a69abc773bbefff51599b72db6"}, ] tqdm = [ - {file = "tqdm-4.59.0-py2.py3-none-any.whl", hash = "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7"}, - {file = "tqdm-4.59.0.tar.gz", hash = "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33"}, + {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, + {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, ] traitlets = [ - {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, - {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, + {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, + {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"}, ] twine = [ - {file = "twine-3.3.0-py3-none-any.whl", hash = "sha256:2f6942ec2a17417e19d2dd372fc4faa424c87ee9ce49b4e20c427eb00a0f3f41"}, - {file = "twine-3.3.0.tar.gz", hash = "sha256:fcffa8fc37e8083a5be0728371f299598870ee1eccc94e9a25cef7b1dcfa8297"}, + {file = "twine-3.8.0-py3-none-any.whl", hash = "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8"}, + {file = "twine-3.8.0.tar.gz", hash = "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -1239,6 +1328,6 @@ yapf = [ {file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] diff --git a/pyproject.toml b/pyproject.toml index 02ba191..0930cda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ commit_subject = "release: Version {version} [skip ci]" commit_author = "LogDNA Bot " [tool.poetry.dependencies] -python = "^3.6" +python = "^3.7" requests = "^2.25.1" [tool.poetry.dev-dependencies] @@ -25,11 +25,11 @@ yapf = "^0.30.0" pytest = "^6.2.2" pytest-cov = "^2.11.1" taskipy = "^1.6.0" -python-semantic-release = "^7.15.3" +python-semantic-release = "^7.28.1" [tool.taskipy.tasks] pre_test = "mkdir -p coverage" -test = "pytest --junitxml=coverage/test.xml --cov=logdna --cov-report=html --verbose" +test = "pytest --junitxml=coverage/test.xml --cov=logdna --cov-report=html --verbose tests/" post_test = "python scripts/json_coverage.py" lint = "flake8 --doctests" "lint:fix" = "yapf -r -i logdna scripts tests" diff --git a/tests/test_logger.py b/tests/test_logger.py index 91e45e0..545ec0b 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -2,15 +2,17 @@ import unittest import requests import time +import os from logdna import LogDNAHandler from concurrent.futures import ThreadPoolExecutor from logdna.configs import defaults from unittest import mock +from unittest.mock import patch now = int(time.time()) expectedLines = [] -LOGDNA_API_KEY = '< YOUR INGESTION KEY HERE >' +LOGDNA_API_KEY = os.environ.get('LOGDNA_INGESTION_KEY') logger = logging.getLogger('logdna') logger.setLevel(logging.INFO) sample_args = { @@ -19,6 +21,7 @@ 'hostname': 'differentHost', 'env': 'differentEnv' } + sample_record = logging.LogRecord('test', logging.INFO, 'test', 5, 'Something to test', [sample_args], '', '', '') @@ -35,13 +38,15 @@ 'lineno': 5 } } + sample_options = { 'hostname': 'localhost', 'ip': '10.0.1.1', 'mac': 'C0:FF:EE:C0:FF:EE', 'tags': 'sample,test', 'index_meta': True, - 'now': int(time.time() * 1000) + 'now': int(time.time() * 1000), + 'retry_interval_secs': 0.5 } @@ -66,7 +71,8 @@ def shutdown(self, wait=True): class LogDNAHandlerTest(unittest.TestCase): - def handler_test(self): + + def test_handler(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) self.assertIsInstance(handler, logging.Handler) self.assertIsInstance(handler.internal_handler, logging.StreamHandler) @@ -93,7 +99,7 @@ def handler_test(self): self.assertEqual(handler.max_concurrent_requests, defaults['MAX_CONCURRENT_REQUESTS']) self.assertEqual(handler.retry_interval_secs, - defaults['RETRY_INTERVAL_SECS']) + sample_options['retry_interval_secs']) # Set the Flush-related Variables self.assertEqual(handler.buf, []) @@ -113,7 +119,7 @@ def handler_test(self): self.assertIsInstance(handler.request_thread_pool, ThreadPoolExecutor) self.assertEqual(handler.level, logging.DEBUG) - def flusher_test(self): + def test_flusher(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) self.assertIsNone(handler.flusher) handler.start_flusher() @@ -121,7 +127,7 @@ def flusher_test(self): handler.close_flusher() self.assertIsNone(handler.flusher) - def emit_test(self): + def test_emit(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.buffer_log = unittest.mock.Mock() handler.emit(sample_record) @@ -129,31 +135,66 @@ def emit_test(self): handler.buffer_log.assert_called_once_with(sample_message) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) - def try_request_test(self): - requests.post = unittest.mock.Mock() - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - requests.post.assert_called_with( - url=handler.url, - json={ - 'e': 'ls', - 'ls': handler.buf - }, - auth=('user', handler.key), - params={ - 'hostname': handler.hostname, - 'ip': handler.ip, - 'mac': handler.mac, - 'tags': handler.tags, - 'now': int(now * 1000) - }, - stream=True, - timeout=handler.request_timeout, - headers={'user-agent': handler.user_agent}) - - def close_test(self): + def test_try_request(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 200 + r.reason = 'OK' + post_mock.return_value = r + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + post_mock.assert_called_with( + url=handler.url, + json={ + 'e': 'ls', + 'ls': handler.buf + }, + auth=('user', handler.key), + params={ + 'hostname': handler.hostname, + 'ip': handler.ip, + 'mac': handler.mac, + 'tags': handler.tags, + 'now': int(now * 1000) + }, + stream=True, + allow_redirects=True, + timeout=handler.request_timeout, + headers={'user-agent': handler.user_agent}) + self.assertFalse(handler.exception_flag) + self.assertTrue(post_mock.call_count, 1) + + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_try_request_500(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 500 + r.reason = 'OK' + post_mock.return_value = r + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + self.assertTrue(handler.exception_flag) + self.assertTrue(post_mock.call_count, 3) + + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_try_request_403(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 403 + r.reason = 'OK' + post_mock.return_value = r + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + self.assertFalse(handler.exception_flag) + self.assertTrue(post_mock.call_count, 1) + + def test_close(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.close_flusher = unittest.mock.Mock() handler.flush_sync = unittest.mock.Mock() @@ -163,7 +204,7 @@ def close_test(self): self.assertIsNone(handler.worker_thread_pool) self.assertIsNone(handler.request_thread_pool) - def flush_test(self): + def test_flush(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.worker_thread_pool = MockThreadPoolExecutor() handler.request_thread_pool = MockThreadPoolExecutor() @@ -172,7 +213,7 @@ def flush_test(self): handler.flush() handler.try_request.assert_called_once_with() - def flush_secondary_test(self): + def test_flush_secondary(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) clean = handler.clean_after_success handler.worker_thread_pool = MockThreadPoolExecutor() @@ -187,12 +228,11 @@ def flush_secondary_test(self): 'e': 'ls', 'ls': [sample_message] }) - clean() self.assertEqual(handler.secondary, []) self.assertEqual(handler.buf, []) - def buffer_log_test(self): + def test_buffer_log(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.worker_thread_pool = MockThreadPoolExecutor() handler.request_thread_pool = MockThreadPoolExecutor() @@ -206,16 +246,6 @@ def buffer_log_test(self): self.assertEqual(handler.buf, [sample_message]) self.assertEqual(handler.buf_size, len(sample_message['line'])) - def test_run_tests(self): - self.handler_test() - self.flusher_test() - self.emit_test() - self.try_request_test() - self.close_test() - self.flush_test() - self.flush_secondary_test() - self.buffer_log_test() - if __name__ == '__main__': unittest.main() From da42cb21f7a3cf9caf64827ce0767d130a451889 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Tue, 10 May 2022 15:58:44 +0000 Subject: [PATCH 116/139] release: Version 1.18.2 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d33be9..2e34481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.2 (2022-05-10) +### Fix +* Requests error handling ([#82](https://github.com/logdna/python/issues/82)) ([`e412859`](https://github.com/logdna/python/commit/e4128592aa9c2301c6467115148f2f23f88da9d3)) + ## v1.18.1 (2021-11-18) ### Fix * **threading:** Account for secondary buffer flush and deadlock ([`d00b952`](https://github.com/logdna/python/commit/d00b9529d116ddf9ba454462d4d804dc54423c83)) diff --git a/logdna/VERSION b/logdna/VERSION index ec6d649..b57fc72 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.1 +1.18.2 diff --git a/pyproject.toml b/pyproject.toml index 0930cda..dc7df10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.1" +version = "1.18.2" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 052a4add6d7e241af007c278a7c0243373fa1d7f Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 18 Nov 2022 11:14:00 -0800 Subject: [PATCH 117/139] doc: updated code examples to fix "ValueError: incomplete format" error (#86) --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5140281..2576fe6 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,14 @@ options = { # Defaults to False; when True meta objects are searchable options['index_meta'] = True +options['custom_fields'] = 'meta' + test = LogDNAHandler(key, options) log.addHandler(test) -log.warning("Warning message", {'app': 'bloop'}) +log.warning("Warning message", extra={'app': 'bloop'}) log.info("Info message") ``` @@ -80,10 +82,10 @@ After initial setup, logging is as easy as: log.info('My Sample Log Line') # Add a custom level -log.info('My Sample Log Line', { 'level': 'MyCustomLevel' }) +log.info('My Sample Log Line', extra={ 'level': 'MyCustomLevel' }) # Include an App name with this specific log -log.info('My Sample Log Line', { 'level': 'Warn', 'app': 'myAppName'}) +log.info('My Sample Log Line', extra={ 'level': 'Warn', 'app': 'myAppName'}) # Pass associated objects along as metadata meta = { @@ -98,7 +100,7 @@ opts = { 'meta': meta } -log.info('My Sample Log Line', opts) +log.info('My Sample Log Line', extra=opts) ``` ### Usage with fileConfig From 75b892f90c7998637423ff6829403bd140d574e0 Mon Sep 17 00:00:00 2001 From: Tony Rogers Date: Wed, 7 Dec 2022 12:11:57 -0500 Subject: [PATCH 118/139] add option to log the ingestion API response on error (#87) --- logdna/logdna.py | 15 ++++++++++++++- tests/test_logger.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index ec20841..a430a2d 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -34,6 +34,7 @@ def __init__(self, key, options={}): self.tags = normalize_list_option(options, 'tags') self.custom_fields = normalize_list_option(options, 'custom_fields') self.custom_fields += defaults['META_FIELDS'] + self.log_error_response = options.get('log_error_response', False) # Set the Connection Variables self.url = options.get('url', defaults['LOGDNA_URL']) @@ -177,7 +178,7 @@ def try_request(self): self.close_flusher() self.exception_flag = True - def send_request(self, data): # noqa: max-complexity: 13 + def send_request(self, data): # noqa: max-complexity: 13 """ Send log data to LogDNA server Returns: @@ -241,23 +242,35 @@ def send_request(self, data): # noqa: max-complexity: 13 self.internalLogger.debug('Unexpected response: %s. ' + 'Discarding flush buffer', reason) + if self.log_error_response: + self.internalLogger.debug( + 'Error Response: %s', response.text) return True # discard if status_code in [401, 403]: self.internalLogger.debug( 'Please provide a valid ingestion key. ' + 'Discarding flush buffer') + if self.log_error_response: + self.internalLogger.debug( + 'Error Response: %s', response.text) return True # discard if 400 <= status_code <= 499: self.internalLogger.debug('Client Error: %s. ' + 'Discarding flush buffer', reason) + if self.log_error_response: + self.internalLogger.debug( + 'Error Response: %s', response.text) return True # discard if status_code in [500, 502, 503, 507]: self.internalLogger.debug('Server Error: %s. Retrying...', reason) + if self.log_error_response: + self.internalLogger.debug( + 'Error Response: %s', response.text) return False # retry self.internalLogger.debug('The request failed: %s.' + diff --git a/tests/test_logger.py b/tests/test_logger.py index 545ec0b..bb177e2 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -194,6 +194,21 @@ def test_try_request_403(self): self.assertFalse(handler.exception_flag) self.assertTrue(post_mock.call_count, 1) + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_try_request_403_log_response(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 403 + r.reason = 'OK' + post_mock.return_value = r + sample_options['log_error_response'] = True + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + self.assertFalse(handler.exception_flag) + self.assertTrue(post_mock.call_count, 1) + def test_close(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.close_flusher = unittest.mock.Mock() From d5bf85ca26579e0186e1abdbcd1b6c44c60c9eca Mon Sep 17 00:00:00 2001 From: Tony Rogers Date: Wed, 7 Dec 2022 12:38:21 -0500 Subject: [PATCH 119/139] fix: add documentation for log_error_response option (#88) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 2576fe6..8db2bf3 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,14 @@ A custom ingestion endpoint to stream log lines into. List of fields out of `record` object to include in the `meta` object. By default, `args`, `name`, `pathname`, and `lineno` will be included. +##### log_error_response + +* _Optional_ +* Type: [bool][] +* Default: `False` + +Enables logging of the API response when an HTTP error is encountered + ### log(line, [options]) #### line From c6f5b7f83ab3408d3f64a5cf6f76eabec32b1d45 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Wed, 7 Dec 2022 17:39:46 +0000 Subject: [PATCH 120/139] release: Version 1.18.3 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e34481..5e737e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.3 (2022-12-07) +### Fix +* Add documentation for log_error_response option ([#88](https://github.com/logdna/python/issues/88)) ([`d5bf85c`](https://github.com/logdna/python/commit/d5bf85ca26579e0186e1abdbcd1b6c44c60c9eca)) + ## v1.18.2 (2022-05-10) ### Fix * Requests error handling ([#82](https://github.com/logdna/python/issues/82)) ([`e412859`](https://github.com/logdna/python/commit/e4128592aa9c2301c6467115148f2f23f88da9d3)) diff --git a/logdna/VERSION b/logdna/VERSION index b57fc72..b9fb27a 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.2 +1.18.3 diff --git a/pyproject.toml b/pyproject.toml index dc7df10..a41ccf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.2" +version = "1.18.3" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 7ccde50ba50c0abbec1a7efe4dd665e8b35511c0 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 6 Jan 2023 08:13:06 -0800 Subject: [PATCH 121/139] fix: Dependabot -> Vulnerabilities -> cryptography >= 37.0.0 < 38.0.3 (#91) --- Jenkinsfile | 0 Makefile | 0 poetry.lock | 691 ++++++++++++++++++++++++++----------------------- pyproject.toml | 2 +- 4 files changed, 363 insertions(+), 330 deletions(-) mode change 100755 => 100644 Jenkinsfile mode change 100755 => 100644 Makefile diff --git a/Jenkinsfile b/Jenkinsfile old mode 100755 new mode 100644 diff --git a/Makefile b/Makefile old mode 100755 new mode 100644 diff --git a/poetry.lock b/poetry.lock index ae55ed5..fc524bf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "atomicwrites" -version = "1.4.0" +version = "1.4.1" description = "Atomic file writes." category = "dev" optional = false @@ -16,17 +16,19 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.4.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=0.971,<0.990)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests_no_zope = ["cloudpickle", "hypothesis", "mypy (>=0.971,<0.990)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "backcall" @@ -38,7 +40,7 @@ python-versions = "*" [[package]] name = "bleach" -version = "5.0.0" +version = "5.0.1" description = "An easy safelist-based HTML-sanitizing tool." category = "dev" optional = false @@ -49,20 +51,20 @@ six = ">=1.9.0" webencodings = "*" [package.extras] -css = ["tinycss2 (>=1.1.0)"] -dev = ["pip-tools (==6.5.1)", "pytest (==7.1.1)", "flake8 (==4.0.1)", "tox (==3.24.5)", "sphinx (==4.3.2)", "twine (==4.0.0)", "wheel (==0.37.1)", "hashin (==0.17.0)", "black (==22.3.0)", "mypy (==0.942)"] +css = ["tinycss2 (>=1.1.0,<1.2)"] +dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cffi" -version = "1.15.0" +version = "1.15.1" description = "Foreign Function Interface for Python calling C code." category = "dev" optional = false @@ -73,11 +75,11 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] @@ -107,11 +109,11 @@ click = "*" [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" @@ -126,7 +128,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "37.0.2" +version = "39.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "dev" optional = false @@ -136,12 +138,12 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "ruff"] +sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] name = "decorator" @@ -153,22 +155,19 @@ python-versions = ">=3.5" [[package]] name = "docutils" -version = "0.18.1" +version = "0.19" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" [[package]] name = "dotty-dict" -version = "1.3.0" +version = "1.3.1" description = "Dictionary wrapper for quick access to deeply nested keys." category = "dev" optional = false -python-versions = "*" - -[package.dependencies] -setuptools_scm = "*" +python-versions = ">=3.5,<4.0" [[package]] name = "flake8" @@ -186,18 +185,18 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "gitdb" -version = "4.0.9" +version = "4.0.10" description = "Git Object Database" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] smmap = ">=3.0.1,<6" [[package]] -name = "gitpython" -version = "3.1.27" +name = "GitPython" +version = "3.1.30" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -209,7 +208,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "idna" -version = "3.3" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -217,7 +216,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.11.3" +version = "6.0.0" description = "Read metadata from Python packages" category = "dev" optional = false @@ -228,9 +227,24 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "5.10.2" +description = "Read resources from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "iniconfig" @@ -242,7 +256,7 @@ python-versions = "*" [[package]] name = "invoke" -version = "1.7.0" +version = "1.7.3" description = "Pythonic task execution" category = "dev" optional = false @@ -250,20 +264,20 @@ python-versions = "*" [[package]] name = "ipdb" -version = "0.13.9" +version = "0.13.11" description = "IPython-enabled pdb" category = "dev" optional = false -python-versions = ">=2.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] decorator = {version = "*", markers = "python_version > \"3.6\""} -ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""} -toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""} +ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} +tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} [[package]] name = "ipython" -version = "7.33.0" +version = "7.34.0" description = "IPython: Productive Interactive Computing" category = "dev" optional = false @@ -280,6 +294,7 @@ pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" +setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -288,14 +303,29 @@ doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] -notebook = ["notebook", "ipywidgets"] +notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] + +[[package]] +name = "jaraco.classes" +version = "3.2.3" +description = "Utility functions for Python class constructs" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "jedi" -version = "0.18.1" +version = "0.18.2" description = "An autocompletion tool for Python that can be used for text editors." category = "dev" optional = false @@ -305,8 +335,9 @@ python-versions = ">=3.6" parso = ">=0.8.0,<0.9.0" [package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jeepney" @@ -317,30 +348,33 @@ optional = false python-versions = ">=3.7" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] -trio = ["trio", "async-generator"] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "23.5.0" +version = "23.13.1" description = "Store and access your passwords safely." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -importlib-metadata = ">=3.6" +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} +"jaraco.classes" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] +completion = ["shtab"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "matplotlib-inline" -version = "0.1.3" +version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" category = "dev" optional = false @@ -357,6 +391,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "more-itertools" +version = "9.0.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "mslex" version = "0.3.0" @@ -367,14 +409,11 @@ python-versions = ">=3.5" [[package]] name = "packaging" -version = "21.3" +version = "22.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +python-versions = ">=3.7" [[package]] name = "parso" @@ -409,14 +448,14 @@ python-versions = "*" [[package]] name = "pkginfo" -version = "1.8.2" +version = "1.9.4" description = "Query metadatdata from sdists / bdists / installed packages." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.extras] -testing = ["coverage", "nose"] +testing = ["pytest", "pytest-cov"] [[package]] name = "pluggy" @@ -435,7 +474,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.29" +version = "3.0.36" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false @@ -446,14 +485,14 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.0" +version = "5.9.4" description = "Cross-platform lib for process and system monitoring in Python." category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "ptyprocess" @@ -496,23 +535,15 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "pygments" -version = "2.12.0" +name = "Pygments" +version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "pyparsing" -version = "3.0.8" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +plugins = ["importlib-metadata"] [[package]] name = "pytest" @@ -550,11 +581,11 @@ pytest = ">=4.6" toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "python-gitlab" -version = "3.4.0" +version = "3.12.0" description = "Interact with GitLab API" category = "dev" optional = false @@ -570,7 +601,7 @@ yaml = ["PyYaml (>=5.2)"] [[package]] name = "python-semantic-release" -version = "7.28.1" +version = "7.32.2" description = "Automatic Semantic Versioning for Python projects" category = "dev" optional = false @@ -582,17 +613,19 @@ click-log = ">=0.3,<1" dotty-dict = ">=1.3.0,<2" gitpython = ">=3.0.8,<4" invoke = ">=1.4.1,<2" +packaging = "*" python-gitlab = ">=2,<4" requests = ">=2.25,<3" semver = ">=2.10,<3" -tomlkit = ">=0.10.0,<0.11.0" +tomlkit = ">=0.10,<1.0" twine = ">=3,<4" +wheel = "*" [package.extras] -dev = ["tox", "isort", "black"] -docs = ["Sphinx (==1.3.6)"] +dev = ["black", "isort", "tox"] +docs = ["Jinja2 (==3.0.3)", "Sphinx (==1.3.6)"] mypy = ["mypy", "types-requests"] -test = ["coverage (>=5,<6)", "pytest (>=5,<6)", "pytest-xdist (>=1,<2)", "pytest-mock (>=2,<3)", "responses (==0.13.3)", "mock (==1.3.0)"] +test = ["coverage (>=5,<6)", "mock (==1.3.0)", "pytest (>=5,<6)", "pytest-mock (>=2,<3)", "pytest-xdist (>=1,<2)", "responses (==0.13.3)"] [[package]] name = "pywin32-ctypes" @@ -604,7 +637,7 @@ python-versions = "*" [[package]] name = "readme-renderer" -version = "35.0" +version = "37.3" description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" category = "dev" optional = false @@ -620,48 +653,33 @@ md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "requests" -version = "2.27.1" +version = "2.28.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-toolbelt" -version = "0.9.1" +version = "0.10.1" description = "A utility belt for advanced users of python-requests" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] requests = ">=2.0.1,<3.0.0" -[[package]] -name = "responses" -version = "0.20.0" -description = "A utility library for mocking out the `requests` Python library." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -requests = ">=2.0,<3.0" -urllib3 = ">=1.25.10" - -[package.extras] -tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-localserver", "flake8", "types-mock", "types-requests", "mypy"] - [[package]] name = "rfc3986" version = "2.0.0" @@ -674,8 +692,8 @@ python-versions = ">=3.7" idna2008 = ["idna"] [[package]] -name = "secretstorage" -version = "3.3.2" +name = "SecretStorage" +version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" category = "dev" optional = false @@ -694,20 +712,17 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "setuptools-scm" -version = "6.4.2" -description = "the blessed package to manage your versions by scm tags" +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false -python-versions = ">=3.6" - -[package.dependencies] -packaging = ">=20.0" -tomli = ">=1.0.0" +python-versions = ">=3.7" [package.extras] -test = ["pytest (>=6.2)", "virtualenv (>20)"] -toml = ["setuptools (>=42)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -734,11 +749,11 @@ optional = false python-versions = "*" [package.extras] -yaml = ["more-itertools", "PyYAML (>=5.1)"] +yaml = ["PyYAML (>=5.1)", "more-itertools"] [[package]] name = "taskipy" -version = "1.10.1" +version = "1.10.3" description = "tasks runner for python projects" category = "dev" optional = false @@ -748,7 +763,7 @@ python-versions = ">=3.6,<4.0" colorama = ">=0.4.4,<0.5.0" mslex = {version = ">=0.3.0,<0.4.0", markers = "sys_platform == \"win32\""} psutil = ">=5.7.2,<6.0.0" -tomli = ">=1.2.3,<2.0.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} [[package]] name = "toml" @@ -760,23 +775,23 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.3" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "tomlkit" -version = "0.10.2" +version = "0.11.6" description = "Style preserving TOML library" category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6" [[package]] name = "tqdm" -version = "4.64.0" +version = "4.64.1" description = "Fast, Extensible Progress Meter" category = "dev" optional = false @@ -793,14 +808,15 @@ telegram = ["requests"] [[package]] name = "traitlets" -version = "5.1.1" +version = "5.8.0" description = "Traitlets Python configuration system" category = "dev" optional = false python-versions = ">=3.7" [package.extras] -test = ["pytest"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] [[package]] name = "twine" @@ -824,7 +840,7 @@ urllib3 = ">=1.26.0" [[package]] name = "typing-extensions" -version = "4.2.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false @@ -832,15 +848,15 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.9" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -859,6 +875,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "wheel" +version = "0.38.4" +description = "A built-package format for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=3.0.0)"] + [[package]] name = "yapf" version = "0.30.0" @@ -869,20 +896,20 @@ python-versions = "*" [[package]] name = "zipp" -version = "3.8.0" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "95a17f1fcf4d2a76cbd338051e679f32f1327891061b7e486b0f5f8ebb759532" +content-hash = "0418105f2bf54e32025584bb913cf422a22714b854ba60b8d8f8c6a6c8345d66" [metadata.files] appnope = [ @@ -890,80 +917,93 @@ appnope = [ {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] bleach = [ - {file = "bleach-5.0.0-py3-none-any.whl", hash = "sha256:08a1fe86d253b5c88c92cc3d810fd8048a16d15762e1e5b74d502256e5926aa1"}, - {file = "bleach-5.0.0.tar.gz", hash = "sha256:c6d6cc054bdc9c83b48b8083e236e5f00f238428666d2ce2e083eaa5fd568565"}, + {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, + {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, @@ -974,8 +1014,8 @@ click-log = [ {file = "click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756"}, ] colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, @@ -1032,102 +1072,117 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] cryptography = [ - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, - {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, - {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, - {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, + {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"}, + {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"}, + {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"}, + {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"}, + {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"}, + {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"}, + {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"}, + {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"}, + {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"}, + {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"}, ] decorator = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, ] dotty-dict = [ - {file = "dotty_dict-1.3.0.tar.gz", hash = "sha256:eb0035a3629ecd84397a68f1f42f1e94abd1c34577a19cd3eacad331ee7cbaf0"}, + {file = "dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f"}, + {file = "dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] gitdb = [ - {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, - {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, + {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, + {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] -gitpython = [ - {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, - {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, +GitPython = [ + {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, + {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, - {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, + {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, + {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, +] +importlib-resources = [ + {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, + {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] invoke = [ - {file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"}, - {file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"}, + {file = "invoke-1.7.3-py3-none-any.whl", hash = "sha256:d9694a865764dd3fd91f25f7e9a97fb41666e822bbb00e670091e3f43933574d"}, + {file = "invoke-1.7.3.tar.gz", hash = "sha256:41b428342d466a82135d5ab37119685a989713742be46e42a3a399d685579314"}, ] ipdb = [ - {file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"}, + {file = "ipdb-0.13.11-py3-none-any.whl", hash = "sha256:f74c2f741c18b909eaf89f19fde973f745ac721744aa1465888ce45813b63a9c"}, + {file = "ipdb-0.13.11.tar.gz", hash = "sha256:c23b6736f01fd4586cc2ecbebdf79a5eb454796853e1cd8f2ed3b7b91d4a3e93"}, ] ipython = [ - {file = "ipython-7.33.0-py3-none-any.whl", hash = "sha256:916a3126896e4fd78dd4d9cf3e21586e7fd93bae3f1cd751588b75524b64bf94"}, - {file = "ipython-7.33.0.tar.gz", hash = "sha256:bcffb865a83b081620301ba0ec4d95084454f26b91d6d66b475bff3dfb0218d4"}, + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, +] +"jaraco.classes" = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, ] jedi = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, ] jeepney = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, ] keyring = [ - {file = "keyring-23.5.0-py3-none-any.whl", hash = "sha256:b0d28928ac3ec8e42ef4cc227822647a19f1d544f21f96457965dc01cf555261"}, - {file = "keyring-23.5.0.tar.gz", hash = "sha256:9012508e141a80bd1c0b6778d5c610dd9f8c464d75ac6774248500503f972fb9"}, + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, ] matplotlib-inline = [ - {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, - {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +more-itertools = [ + {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, + {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, +] mslex = [ {file = "mslex-0.3.0-py2.py3-none-any.whl", hash = "sha256:380cb14abf8fabf40e56df5c8b21a6d533dc5cbdcfe42406bbf08dda8f42e42a"}, {file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"}, ] packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, @@ -1142,50 +1197,32 @@ pickleshare = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] pkginfo = [ - {file = "pkginfo-1.8.2-py2.py3-none-any.whl", hash = "sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc"}, - {file = "pkginfo-1.8.2.tar.gz", hash = "sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff"}, + {file = "pkginfo-1.9.4-py3-none-any.whl", hash = "sha256:7fc056f8e6b93355925083373b0f6bfbabe84ee3f29854fd155c9d6a4211267d"}, + {file = "pkginfo-1.9.4.tar.gz", hash = "sha256:e769fd353593d43e0c9f47e17e25f09a8efcddcdf9a71674ea3ba444ff31bb44"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, - {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, + {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, + {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, ] psutil = [ - {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, - {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, - {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"}, - {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"}, - {file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"}, - {file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"}, - {file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"}, - {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"}, - {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"}, - {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"}, - {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"}, - {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"}, - {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"}, - {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"}, - {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"}, - {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"}, - {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"}, - {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"}, - {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"}, - {file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"}, - {file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"}, - {file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"}, - {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"}, - {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"}, - {file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"}, - {file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"}, - {file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"}, - {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"}, - {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"}, - {file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"}, - {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, - {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -1207,13 +1244,9 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, -] -pyparsing = [ - {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, - {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, +Pygments = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, ] pytest = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, @@ -1224,48 +1257,44 @@ pytest-cov = [ {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] python-gitlab = [ - {file = "python-gitlab-3.4.0.tar.gz", hash = "sha256:6180b81ee2f265ad8d8412956a1740b4d3ceca7b28ae2f707dfe62375fed0082"}, - {file = "python_gitlab-3.4.0-py3-none-any.whl", hash = "sha256:251b63f0589d51f854516948c84e9eb8df26e1e9dea595cf86b43f17c43007dd"}, + {file = "python-gitlab-3.12.0.tar.gz", hash = "sha256:567390c2b93690dae62ed9738bf9f221fa45c01378fdf896089dbf7c8a134fbd"}, + {file = "python_gitlab-3.12.0-py3-none-any.whl", hash = "sha256:a5eb36b49783fda34563376674d5251dbbdbd1abd23b287dadf82f67d861b2c1"}, ] python-semantic-release = [ - {file = "python-semantic-release-7.28.1.tar.gz", hash = "sha256:d7f82b3d4c06b304d07689b8a1c7d0d448ff07c2ab81cd810c4f2f900f24c599"}, - {file = "python_semantic_release-7.28.1-py3-none-any.whl", hash = "sha256:319c3e811d6e10bc3f0e967419eae0e5e9ba1e6745aa2fd94dd24e3995541ca2"}, + {file = "python-semantic-release-7.32.2.tar.gz", hash = "sha256:4d8f5d20680723e1329765b6f3e28b43f058fd1ef5f423f6f95397cd927c3ebc"}, + {file = "python_semantic_release-7.32.2-py3-none-any.whl", hash = "sha256:9fcf82f403b91a61e58728ea05e2e2e25010ce9ed07830fe78251819b4b834d9"}, ] pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] readme-renderer = [ - {file = "readme_renderer-35.0-py3-none-any.whl", hash = "sha256:73b84905d091c31f36e50b4ae05ae2acead661f6a09a9abb4df7d2ddcdb6a698"}, - {file = "readme_renderer-35.0.tar.gz", hash = "sha256:a727999acfc222fc21d82a12ed48c957c4989785e5865807c65a487d21677497"}, + {file = "readme_renderer-37.3-py3-none-any.whl", hash = "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343"}, + {file = "readme_renderer-37.3.tar.gz", hash = "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273"}, ] requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] requests-toolbelt = [ - {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, - {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, -] -responses = [ - {file = "responses-0.20.0-py3-none-any.whl", hash = "sha256:18831bc2d72443b67664d98038374a6fa1f27eaaff4dd9a7d7613723416fea3c"}, - {file = "responses-0.20.0.tar.gz", hash = "sha256:644905bc4fb8a18fa37e3882b2ac05e610fe8c2f967d327eed669e314d94a541"}, + {file = "requests-toolbelt-0.10.1.tar.gz", hash = "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d"}, + {file = "requests_toolbelt-0.10.1-py2.py3-none-any.whl", hash = "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7"}, ] rfc3986 = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, ] -secretstorage = [ - {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, - {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, +SecretStorage = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -setuptools-scm = [ - {file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"}, - {file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"}, +setuptools = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1280,40 +1309,40 @@ smmap = [ {file = "tap.py-3.1.tar.gz", hash = "sha256:3c0cd45212ad5a25b35445964e2517efa000a118a1bfc3437dae828892eaf1e1"}, ] taskipy = [ - {file = "taskipy-1.10.1-py3-none-any.whl", hash = "sha256:9b38333654da487b6d16de6fa330b7629d1935d1e74819ba4c5f17a1c372d37b"}, - {file = "taskipy-1.10.1.tar.gz", hash = "sha256:6fa0b11c43d103e376063e90be31d87b435aad50fb7dc1c9a2de9b60a85015ed"}, + {file = "taskipy-1.10.3-py3-none-any.whl", hash = "sha256:4c0070ca53868d97989f7ab5c6f237525d52ee184f9b967576e8fe427ed9d0b8"}, + {file = "taskipy-1.10.3.tar.gz", hash = "sha256:112beaf21e3d5569950b99162a1de003fa885fabee9e450757a6b874be914877"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, - {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tomlkit = [ - {file = "tomlkit-0.10.2-py3-none-any.whl", hash = "sha256:905cf92c2111ef80d355708f47ac24ad1b6fc2adc5107455940088c9bbecaedb"}, - {file = "tomlkit-0.10.2.tar.gz", hash = "sha256:30d54c0b914e595f3d10a87888599eab5321a2a69abc773bbefff51599b72db6"}, + {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, + {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, ] tqdm = [ - {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, - {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, + {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"}, + {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"}, ] traitlets = [ - {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, - {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"}, + {file = "traitlets-5.8.0-py3-none-any.whl", hash = "sha256:c864831efa0ba6576d09b44884b34e41defc18c0d7e720b4a2d6698c842cab3e"}, + {file = "traitlets-5.8.0.tar.gz", hash = "sha256:6cc57d6dc28c85d5365961726ffd19b538739347749e13ebe34e03323a0e8f84"}, ] twine = [ {file = "twine-3.8.0-py3-none-any.whl", hash = "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8"}, {file = "twine-3.8.0.tar.gz", hash = "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -1323,11 +1352,15 @@ webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] +wheel = [ + {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, + {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, +] yapf = [ {file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"}, {file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"}, ] zipp = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, ] diff --git a/pyproject.toml b/pyproject.toml index a41ccf3..31960c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ commit_author = "LogDNA Bot " [tool.poetry.dependencies] python = "^3.7" -requests = "^2.25.1" +requests = "^2.28.1" [tool.poetry.dev-dependencies] coverage = "^5.4" From 834dd9aac5f763c7eb462d47c175f76f1aa442b5 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Fri, 6 Jan 2023 16:17:15 +0000 Subject: [PATCH 122/139] release: Version 1.18.4 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e737e5..0a58667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.4 (2023-01-06) +### Fix +* Dependabot -> Vulnerabilities -> cryptography >= 37.0.0 < 38.0.3 ([#91](https://github.com/logdna/python/issues/91)) ([`7ccde50`](https://github.com/logdna/python/commit/7ccde50ba50c0abbec1a7efe4dd665e8b35511c0)) + ## v1.18.3 (2022-12-07) ### Fix * Add documentation for log_error_response option ([#88](https://github.com/logdna/python/issues/88)) ([`d5bf85c`](https://github.com/logdna/python/commit/d5bf85ca26579e0186e1abdbcd1b6c44c60c9eca)) diff --git a/logdna/VERSION b/logdna/VERSION index b9fb27a..a67b05e 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.3 +1.18.4 diff --git a/pyproject.toml b/pyproject.toml index 31960c5..3b661cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.3" +version = "1.18.4" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 905b06a648e19896087b0c51ba1e055212727560 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 27 Jan 2023 08:49:38 -0800 Subject: [PATCH 123/139] fix(chore): upgraded pytest to resolve security vulnerability in py <= 1.11.0 (#92) --- poetry.lock | 698 ++++++++++--------------------------------------- pyproject.toml | 2 +- 2 files changed, 143 insertions(+), 557 deletions(-) diff --git a/poetry.lock b/poetry.lock index fc524bf..c7fb7c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,14 +6,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "attrs" version = "22.2.0" @@ -23,12 +15,12 @@ optional = false python-versions = ">=3.6" [package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=0.971,<0.990)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests_no_zope = ["cloudpickle", "hypothesis", "mypy (>=0.971,<0.990)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs"] +docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["attrs", "zope.interface"] +tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] +tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] [[package]] name = "backcall" @@ -40,7 +32,7 @@ python-versions = "*" [[package]] name = "bleach" -version = "5.0.1" +version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." category = "dev" optional = false @@ -52,7 +44,6 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] -dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] [[package]] name = "certifi" @@ -75,14 +66,11 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.0.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode_backport = ["unicodedata2"] +python-versions = "*" [[package]] name = "click" @@ -139,11 +127,11 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "ruff"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "decorator" @@ -169,6 +157,17 @@ category = "dev" optional = false python-versions = ">=3.5,<4.0" +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "flake8" version = "3.9.2" @@ -195,7 +194,7 @@ python-versions = ">=3.7" smmap = ">=3.0.1,<6" [[package]] -name = "GitPython" +name = "gitpython" version = "3.1.30" description = "GitPython is a python library used to interact with Git repositories" category = "dev" @@ -227,9 +226,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" @@ -243,16 +242,16 @@ python-versions = ">=3.7" zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "invoke" @@ -271,8 +270,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] -decorator = {version = "*", markers = "python_version > \"3.6\""} -ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} +decorator = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\" or python_version >= \"3.11\""} +ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\" and python_version < \"3.11\" or python_version >= \"3.11\""} tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} [[package]] @@ -294,7 +293,6 @@ pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" -setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -303,10 +301,10 @@ doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] +notebook = ["notebook", "ipywidgets"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] [[package]] name = "jaraco.classes" @@ -320,8 +318,8 @@ python-versions = ">=3.7" more-itertools = "*" [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [[package]] name = "jedi" @@ -335,7 +333,7 @@ python-versions = ">=3.6" parso = ">=0.8.0,<0.9.0" [package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx-rtd-theme (==0.4.3)", "sphinx (==1.8.5)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] @@ -348,8 +346,8 @@ optional = false python-versions = ">=3.7" [package.extras] -test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] +test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +trio = ["trio", "async-generator"] [[package]] name = "keyring" @@ -369,8 +367,8 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab"] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [[package]] name = "matplotlib-inline" @@ -409,7 +407,7 @@ python-versions = ">=3.5" [[package]] name = "packaging" -version = "22.0" +version = "23.0" description = "Core utilities for Python packages" category = "dev" optional = false @@ -448,8 +446,8 @@ python-versions = "*" [[package]] name = "pkginfo" -version = "1.9.4" -description = "Query metadatdata from sdists / bdists / installed packages." +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." category = "dev" optional = false python-versions = ">=3.6" @@ -492,7 +490,7 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +test = ["ipaddress", "mock", "enum34", "pywin32", "wmi"] [[package]] name = "ptyprocess" @@ -502,14 +500,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "pycodestyle" version = "2.7.0" @@ -535,7 +525,7 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "Pygments" +name = "pygments" version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" @@ -547,25 +537,24 @@ plugins = ["importlib-metadata"] [[package]] name = "pytest" -version = "6.2.5" +version = "7.2.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" @@ -581,7 +570,7 @@ pytest = ">=4.6" toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "python-gitlab" @@ -601,7 +590,7 @@ yaml = ["PyYaml (>=5.2)"] [[package]] name = "python-semantic-release" -version = "7.32.2" +version = "7.33.0" description = "Automatic Semantic Versioning for Python projects" category = "dev" optional = false @@ -619,13 +608,12 @@ requests = ">=2.25,<3" semver = ">=2.10,<3" tomlkit = ">=0.10,<1.0" twine = ">=3,<4" -wheel = "*" [package.extras] -dev = ["black", "isort", "tox"] -docs = ["Jinja2 (==3.0.3)", "Sphinx (==1.3.6)"] +dev = ["tox", "isort", "black"] +docs = ["Sphinx (==1.3.6)", "Jinja2 (==3.0.3)"] mypy = ["mypy", "types-requests"] -test = ["coverage (>=5,<6)", "mock (==1.3.0)", "pytest (>=5,<6)", "pytest-mock (>=2,<3)", "pytest-xdist (>=1,<2)", "responses (==0.13.3)"] +test = ["coverage (>=5,<6)", "pytest (>=7,<8)", "pytest-xdist (>=1,<2)", "pytest-mock (>=2,<3)", "responses (==0.13.3)", "mock (==1.3.0)"] [[package]] name = "pywin32-ctypes" @@ -653,7 +641,7 @@ md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "requests" -version = "2.28.1" +version = "2.28.2" description = "Python HTTP for Humans." category = "main" optional = false @@ -661,7 +649,7 @@ python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" @@ -692,7 +680,7 @@ python-versions = ">=3.7" idna2008 = ["idna"] [[package]] -name = "SecretStorage" +name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" category = "dev" @@ -711,19 +699,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "setuptools" -version = "65.6.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -749,7 +724,7 @@ optional = false python-versions = "*" [package.extras] -yaml = ["PyYAML (>=5.1)", "more-itertools"] +yaml = ["more-itertools", "PyYAML (>=5.1)"] [[package]] name = "taskipy" @@ -808,7 +783,7 @@ telegram = ["requests"] [[package]] name = "traitlets" -version = "5.8.0" +version = "5.8.1" description = "Traitlets Python configuration system" category = "dev" optional = false @@ -848,20 +823,20 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.13" +version = "1.26.14" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "wcwidth" -version = "0.2.5" +version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" category = "dev" optional = false @@ -875,17 +850,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "wheel" -version = "0.38.4" -description = "A built-package format for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["pytest (>=3.0.0)"] - [[package]] name = "yapf" version = "0.30.0" @@ -903,464 +867,86 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "0418105f2bf54e32025584bb913cf422a22714b854ba60b8d8f8c6a6c8345d66" +content-hash = "2ce3c41b1d2a1222b4e53674ab5901777d35e52bcee9eaeb93ff625c74e14d31" [metadata.files] -appnope = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -backcall = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] -bleach = [ - {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, - {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -click-log = [ - {file = "click-log-0.4.0.tar.gz", hash = "sha256:3970f8570ac54491237bcdb3d8ab5e3eef6c057df29f8c3d1151a51a9c23b975"}, - {file = "click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, -] -cryptography = [ - {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288"}, - {file = "cryptography-39.0.0-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96"}, - {file = "cryptography-39.0.0-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717"}, - {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df"}, - {file = "cryptography-39.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1"}, - {file = "cryptography-39.0.0-cp36-abi3-win32.whl", hash = "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de"}, - {file = "cryptography-39.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190"}, - {file = "cryptography-39.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8"}, - {file = "cryptography-39.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39"}, - {file = "cryptography-39.0.0.tar.gz", hash = "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf"}, -] -decorator = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] -docutils = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] -dotty-dict = [ - {file = "dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f"}, - {file = "dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15"}, -] -flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, -] -gitdb = [ - {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, - {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, -] -GitPython = [ - {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, - {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, - {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, -] -importlib-resources = [ - {file = "importlib_resources-5.10.2-py3-none-any.whl", hash = "sha256:7d543798b0beca10b6a01ac7cafda9f822c54db9e8376a6bf57e0cbd74d486b6"}, - {file = "importlib_resources-5.10.2.tar.gz", hash = "sha256:e4a96c8cc0339647ff9a5e0550d9f276fc5a01ffa276012b58ec108cfd7b8484"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -invoke = [ - {file = "invoke-1.7.3-py3-none-any.whl", hash = "sha256:d9694a865764dd3fd91f25f7e9a97fb41666e822bbb00e670091e3f43933574d"}, - {file = "invoke-1.7.3.tar.gz", hash = "sha256:41b428342d466a82135d5ab37119685a989713742be46e42a3a399d685579314"}, -] -ipdb = [ - {file = "ipdb-0.13.11-py3-none-any.whl", hash = "sha256:f74c2f741c18b909eaf89f19fde973f745ac721744aa1465888ce45813b63a9c"}, - {file = "ipdb-0.13.11.tar.gz", hash = "sha256:c23b6736f01fd4586cc2ecbebdf79a5eb454796853e1cd8f2ed3b7b91d4a3e93"}, -] -ipython = [ - {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, - {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, -] -"jaraco.classes" = [ - {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, - {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, -] -jedi = [ - {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, - {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, -] -jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, -] -keyring = [ - {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, - {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, -] -matplotlib-inline = [ - {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, - {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -more-itertools = [ - {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, - {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, -] -mslex = [ - {file = "mslex-0.3.0-py2.py3-none-any.whl", hash = "sha256:380cb14abf8fabf40e56df5c8b21a6d533dc5cbdcfe42406bbf08dda8f42e42a"}, - {file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"}, -] -packaging = [ - {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, - {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, -] -parso = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, -] -pexpect = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, -] -pickleshare = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] -pkginfo = [ - {file = "pkginfo-1.9.4-py3-none-any.whl", hash = "sha256:7fc056f8e6b93355925083373b0f6bfbabe84ee3f29854fd155c9d6a4211267d"}, - {file = "pkginfo-1.9.4.tar.gz", hash = "sha256:e769fd353593d43e0c9f47e17e25f09a8efcddcdf9a71674ea3ba444ff31bb44"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -prompt-toolkit = [ - {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, - {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, -] -psutil = [ - {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, - {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, - {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, - {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, - {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, - {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, - {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, - {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, -] -ptyprocess = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, -] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] -pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] -Pygments = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, -] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] -pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, -] -python-gitlab = [ - {file = "python-gitlab-3.12.0.tar.gz", hash = "sha256:567390c2b93690dae62ed9738bf9f221fa45c01378fdf896089dbf7c8a134fbd"}, - {file = "python_gitlab-3.12.0-py3-none-any.whl", hash = "sha256:a5eb36b49783fda34563376674d5251dbbdbd1abd23b287dadf82f67d861b2c1"}, -] -python-semantic-release = [ - {file = "python-semantic-release-7.32.2.tar.gz", hash = "sha256:4d8f5d20680723e1329765b6f3e28b43f058fd1ef5f423f6f95397cd927c3ebc"}, - {file = "python_semantic_release-7.32.2-py3-none-any.whl", hash = "sha256:9fcf82f403b91a61e58728ea05e2e2e25010ce9ed07830fe78251819b4b834d9"}, -] -pywin32-ctypes = [ - {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, - {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, -] -readme-renderer = [ - {file = "readme_renderer-37.3-py3-none-any.whl", hash = "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343"}, - {file = "readme_renderer-37.3.tar.gz", hash = "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -requests-toolbelt = [ - {file = "requests-toolbelt-0.10.1.tar.gz", hash = "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d"}, - {file = "requests_toolbelt-0.10.1-py2.py3-none-any.whl", hash = "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7"}, -] -rfc3986 = [ - {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, - {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, -] -SecretStorage = [ - {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, - {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, -] -semver = [ - {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, - {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, -] -setuptools = [ - {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, - {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -smmap = [ - {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, - {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, -] -"tap.py" = [ - {file = "tap.py-3.1-py3-none-any.whl", hash = "sha256:928c852f3361707b796c93730cc5402c6378660b161114461066acf53d65bf5d"}, - {file = "tap.py-3.1.tar.gz", hash = "sha256:3c0cd45212ad5a25b35445964e2517efa000a118a1bfc3437dae828892eaf1e1"}, -] -taskipy = [ - {file = "taskipy-1.10.3-py3-none-any.whl", hash = "sha256:4c0070ca53868d97989f7ab5c6f237525d52ee184f9b967576e8fe427ed9d0b8"}, - {file = "taskipy-1.10.3.tar.gz", hash = "sha256:112beaf21e3d5569950b99162a1de003fa885fabee9e450757a6b874be914877"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -tomlkit = [ - {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, - {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, -] -tqdm = [ - {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"}, - {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"}, -] -traitlets = [ - {file = "traitlets-5.8.0-py3-none-any.whl", hash = "sha256:c864831efa0ba6576d09b44884b34e41defc18c0d7e720b4a2d6698c842cab3e"}, - {file = "traitlets-5.8.0.tar.gz", hash = "sha256:6cc57d6dc28c85d5365961726ffd19b538739347749e13ebe34e03323a0e8f84"}, -] -twine = [ - {file = "twine-3.8.0-py3-none-any.whl", hash = "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8"}, - {file = "twine-3.8.0.tar.gz", hash = "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] -urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, -] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] -webencodings = [ - {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, - {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, -] -wheel = [ - {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, - {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, -] -yapf = [ - {file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"}, - {file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"}, -] -zipp = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] +appnope = [] +attrs = [] +backcall = [] +bleach = [] +certifi = [] +cffi = [] +charset-normalizer = [] +click = [] +click-log = [] +colorama = [] +coverage = [] +cryptography = [] +decorator = [] +docutils = [] +dotty-dict = [] +exceptiongroup = [] +flake8 = [] +gitdb = [] +gitpython = [] +idna = [] +importlib-metadata = [] +importlib-resources = [] +iniconfig = [] +invoke = [] +ipdb = [] +ipython = [] +"jaraco.classes" = [] +jedi = [] +jeepney = [] +keyring = [] +matplotlib-inline = [] +mccabe = [] +more-itertools = [] +mslex = [] +packaging = [] +parso = [] +pexpect = [] +pickleshare = [] +pkginfo = [] +pluggy = [] +prompt-toolkit = [] +psutil = [] +ptyprocess = [] +pycodestyle = [] +pycparser = [] +pyflakes = [] +pygments = [] +pytest = [] +pytest-cov = [] +python-gitlab = [] +python-semantic-release = [] +pywin32-ctypes = [] +readme-renderer = [] +requests = [] +requests-toolbelt = [] +rfc3986 = [] +secretstorage = [] +semver = [] +six = [] +smmap = [] +"tap.py" = [] +taskipy = [] +toml = [] +tomli = [] +tomlkit = [] +tqdm = [] +traitlets = [] +twine = [] +typing-extensions = [] +urllib3 = [] +wcwidth = [] +webencodings = [] +yapf = [] +zipp = [] diff --git a/pyproject.toml b/pyproject.toml index 3b661cd..72f00e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ coverage = "^5.4" ipdb = "^0.13.4" flake8 = "^3.8.4" yapf = "^0.30.0" -pytest = "^6.2.2" +pytest = "^7.2.0" pytest-cov = "^2.11.1" taskipy = "^1.6.0" python-semantic-release = "^7.28.1" From 6ea7450cb0400d37a79c371822fa4c7ac0818f32 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Fri, 27 Jan 2023 16:53:21 +0000 Subject: [PATCH 124/139] release: Version 1.18.5 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a58667..e779eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.5 (2023-01-27) +### Fix +* **chore:** Upgraded pytest to resolve security vulnerability in py <= 1.11.0 ([#92](https://github.com/logdna/python/issues/92)) ([`905b06a`](https://github.com/logdna/python/commit/905b06a648e19896087b0c51ba1e055212727560)) + ## v1.18.4 (2023-01-06) ### Fix * Dependabot -> Vulnerabilities -> cryptography >= 37.0.0 < 38.0.3 ([#91](https://github.com/logdna/python/issues/91)) ([`7ccde50`](https://github.com/logdna/python/commit/7ccde50ba50c0abbec1a7efe4dd665e8b35511c0)) diff --git a/logdna/VERSION b/logdna/VERSION index a67b05e..8e8b0a9 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.4 +1.18.5 diff --git a/pyproject.toml b/pyproject.toml index 72f00e8..353354e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.4" +version = "1.18.5" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 712d81d4ab2bfbf95d65898fb365a5bcb1396199 Mon Sep 17 00:00:00 2001 From: Dmitri Khokhlov Date: Fri, 27 Jan 2023 14:04:28 -0800 Subject: [PATCH 125/139] fix: added retries for http 429 504 (#93) - added retries for http 429 504 LOG-11814 --- logdna/logdna.py | 14 +++++++++++-- tests/test_logger.py | 48 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index a430a2d..7597f45 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -212,8 +212,10 @@ def send_request(self, data): # noqa: max-complexity: 13 3XX unexpected status 401, 403 expected client error, invalid ingestion key + 429 expected server error, + "client error", transient 4XX unexpected client error - 500 502 503 507 expected server error, transient + 500 502 503 504 507 expected server error, transient 5XX unexpected server error handling: expected status discard flush buffer @@ -256,6 +258,14 @@ def send_request(self, data): # noqa: max-complexity: 13 'Error Response: %s', response.text) return True # discard + if status_code == 429: + self.internalLogger.debug('Client Error: %s. Retrying...', + reason) + if self.log_error_response: + self.internalLogger.debug( + 'Error Response: %s', response.text) + return False # retry + if 400 <= status_code <= 499: self.internalLogger.debug('Client Error: %s. ' + 'Discarding flush buffer', @@ -265,7 +275,7 @@ def send_request(self, data): # noqa: max-complexity: 13 'Error Response: %s', response.text) return True # discard - if status_code in [500, 502, 503, 507]: + if status_code in [500, 502, 503, 504, 507]: self.internalLogger.debug('Server Error: %s. Retrying...', reason) if self.log_error_response: diff --git a/tests/test_logger.py b/tests/test_logger.py index bb177e2..6ee1a08 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -171,7 +171,49 @@ def test_try_request_500(self): with patch('requests.post') as post_mock: r = requests.Response() r.status_code = 500 - r.reason = 'OK' + r.reason = 'Internal Server Error' + post_mock.return_value = r + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + self.assertTrue(handler.exception_flag) + self.assertTrue(post_mock.call_count, 3) + + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_try_request_502(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 502 + r.reason = 'Bad Gateway' + post_mock.return_value = r + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + self.assertTrue(handler.exception_flag) + self.assertTrue(post_mock.call_count, 3) + + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_try_request_504(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 504 + r.reason = 'Gateway Timeout' + post_mock.return_value = r + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + sample_message['timestamp'] = unittest.mock.ANY + handler.buf = [sample_message] + handler.try_request() + self.assertTrue(handler.exception_flag) + self.assertTrue(post_mock.call_count, 3) + + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_try_request_429(self): + with patch('requests.post') as post_mock: + r = requests.Response() + r.status_code = 429 + r.reason = 'Too Many Requests' post_mock.return_value = r handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY @@ -185,7 +227,7 @@ def test_try_request_403(self): with patch('requests.post') as post_mock: r = requests.Response() r.status_code = 403 - r.reason = 'OK' + r.reason = 'Forbidden' post_mock.return_value = r handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY @@ -199,7 +241,7 @@ def test_try_request_403_log_response(self): with patch('requests.post') as post_mock: r = requests.Response() r.status_code = 403 - r.reason = 'OK' + r.reason = 'Forbidden' post_mock.return_value = r sample_options['log_error_response'] = True handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) From 1d43ec6c0314bcc9b6dfe037376abc11494838ad Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Fri, 27 Jan 2023 22:08:39 +0000 Subject: [PATCH 126/139] release: Version 1.18.6 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e779eb2..d7fc86d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.6 (2023-01-27) +### Fix +* Added retries for http 429 504 ([#93](https://github.com/logdna/python/issues/93)) ([`712d81d`](https://github.com/logdna/python/commit/712d81d4ab2bfbf95d65898fb365a5bcb1396199)) + ## v1.18.5 (2023-01-27) ### Fix * **chore:** Upgraded pytest to resolve security vulnerability in py <= 1.11.0 ([#92](https://github.com/logdna/python/issues/92)) ([`905b06a`](https://github.com/logdna/python/commit/905b06a648e19896087b0c51ba1e055212727560)) diff --git a/logdna/VERSION b/logdna/VERSION index 8e8b0a9..04a8bc2 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.5 +1.18.6 diff --git a/pyproject.toml b/pyproject.toml index 353354e..34dc1c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.5" +version = "1.18.6" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 97803b55a102539a75b0763891c1d27757460067 Mon Sep 17 00:00:00 2001 From: Eric Satterwhite Date: Fri, 5 May 2023 16:25:53 -0500 Subject: [PATCH 127/139] fix: gate buils from non maintainers adds a gate to the build that prevents builds from auto triggering from people outside the org --- Jenkinsfile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index f4f00ee..a516452 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,6 +22,17 @@ pipeline { } stages { + stage('Validate PR Source') { + when { + expression { env.CHANGE_FORK } + not { + triggeredBy 'issueCommentCause' + } + } + steps { + error("A maintainer needs to approve this PR for CI by commenting") + } + } stage('Test') { steps { From 90d5cdfb9f189d0b4c38e633df70fd95046f66df Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Fri, 5 May 2023 21:34:03 +0000 Subject: [PATCH 128/139] release: Version 1.18.7 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7fc86d..cbec101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.7 (2023-05-05) +### Fix +* Gate buils from non maintainers ([`97803b5`](https://github.com/logdna/python/commit/97803b55a102539a75b0763891c1d27757460067)) + ## v1.18.6 (2023-01-27) ### Fix * Added retries for http 429 504 ([#93](https://github.com/logdna/python/issues/93)) ([`712d81d`](https://github.com/logdna/python/commit/712d81d4ab2bfbf95d65898fb365a5bcb1396199)) diff --git a/logdna/VERSION b/logdna/VERSION index 04a8bc2..d6f3a38 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.6 +1.18.7 diff --git a/pyproject.toml b/pyproject.toml index 34dc1c3..188319d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.6" +version = "1.18.7" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 033a63f7043e2e48889be23a7e90446ba47cb71b Mon Sep 17 00:00:00 2001 From: Stephen Hawkins <139154814+shawkinsmezmo@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:12:19 -0700 Subject: [PATCH 129/139] LOG-15414 - Fix race condition that results in dropped logs (#100) * Fix race condition that results in dropped logs * Address PR feedback: simplify locking semantics around buffer access * Address PR feedback: Remove exception flag, minor test improvements --------- Co-authored-by: Stephen Hawkins --- logdna/logdna.py | 144 ++++++++++++----------- tests/test_logger.py | 268 +++++++++++++++++++++++-------------------- 2 files changed, 221 insertions(+), 191 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 7597f45..3a5043a 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -53,9 +53,7 @@ def __init__(self, key, options={}): # Set the Flush-related Variables self.buf = [] self.buf_size = 0 - self.secondary = [] - self.exception_flag = False - self.flusher = None + self.include_standard_meta = options.get('include_standard_meta', None) if self.include_standard_meta is not None: @@ -78,14 +76,23 @@ def __init__(self, key, options={}): self.setLevel(logging.DEBUG) self.lock = threading.RLock() - def start_flusher(self): - if not self.flusher: - self.flusher = threading.Timer(self.flush_interval_secs, - self.flush) - self.flusher.start() + # Start the flusher + self.flusher_stopped = threading.Event() + self.flusher = threading.Timer( + self.flush_interval_secs, self.flush_timer_worker) + self.flusher.start() + + def flush_timer_worker(self): + while not self.flusher_stopped.wait(self.flush_interval_secs): + try: + self.flush() + except Exception as e: + self.internalLogger.exception( + f'Error in flush_timer_worker: {e}') def close_flusher(self): if self.flusher: + self.flusher_stopped.set() self.flusher.cancel() self.flusher = None @@ -99,72 +106,61 @@ def buffer_log(self, message): self.internalLogger.debug('Error in calling buffer_log: %s', e) def buffer_log_sync(self, message): - # Attempt to acquire lock to write to buf - # otherwise write to secondary as flush occurs - if self.lock.acquire(blocking=False): - msglen = len(message['line']) - if self.buf_size + msglen < self.buf_retention_limit: - self.buf.append(message) - self.buf_size += msglen - else: - self.internalLogger.debug( - 'The buffer size exceeded the limit: %s', - self.buf_retention_limit) - - if self.buf_size >= self.flush_limit and not self.exception_flag: - self.close_flusher() - self.flush() - else: - self.start_flusher() - self.lock.release() - else: - self.secondary.append(message) + # Attempt to acquire lock to write to buffer + if self.lock.acquire(blocking=True): + try: + msglen = len(message['line']) + if self.buf_size + msglen < self.buf_retention_limit: + self.buf.append(message) + self.buf_size += msglen + else: + self.internalLogger.debug( + 'The buffer size exceeded the limit: %s', + self.buf_retention_limit) - def clean_after_success(self): - self.close_flusher() - self.buf.clear() - self.buf_size = 0 - self.exception_flag = False + if self.buf_size >= self.flush_limit: + self.flush() + except Exception as e: + self.internalLogger.exception(f'Error in buffer_log_sync: {e}') + finally: + self.lock.release() def flush(self): - if self.worker_thread_pool: + self.schedule_flush_sync() + + def schedule_flush_sync(self, should_block=False): + if self.request_thread_pool: try: - self.worker_thread_pool.submit(self.flush_sync) + self.request_thread_pool.submit( + self.try_lock_and_do_flush_request, should_block) except RuntimeError: - self.flush_sync() + self.try_lock_and_do_flush_request(should_block) except Exception as e: - self.internalLogger.debug('Error in calling flush: %s', e) - - def flush_sync(self): - if self.buf_size == 0 and len(self.secondary) == 0: - return + self.internalLogger.debug( + 'Error in calling try_lock_and_do_flush_request: %s', e) - if self.lock.acquire(blocking=False): - if self.request_thread_pool: - try: - self.request_thread_pool.submit(self.try_request) - except RuntimeError: - self.try_request() - except Exception as e: - self.internalLogger.debug( - 'Error in calling try_request: %s', e) - finally: - self.lock.release() - else: + def try_lock_and_do_flush_request(self, should_block=False): + local_buf = [] + if self.lock.acquire(blocking=should_block): + if not self.buf: self.lock.release() - else: - self.close_flusher() - self.start_flusher() - - def try_request(self): - self.buf.extend(self.secondary) - self.secondary = [] - data = {'e': 'ls', 'ls': self.buf} + return + + if self.buf: + local_buf = self.buf.copy() + self.buf.clear() + self.buf_size = 0 + self.lock.release() + + if local_buf: + self.try_request(local_buf) + + def try_request(self, buf): + data = {'e': 'ls', 'ls': buf} retries = 0 while retries < self.max_retry_attempts: retries += 1 if self.send_request(data): - self.clean_after_success() break sleep_time = self.retry_interval_secs * (1 << (retries - 1)) @@ -175,8 +171,6 @@ def try_request(self): self.internalLogger.debug( 'Flush exceeded %s tries. Discarding flush buffer', self.max_retry_attempts) - self.close_flusher() - self.exception_flag = True def send_request(self, data): # noqa: max-complexity: 13 """ @@ -331,11 +325,29 @@ def emit(self, record): self.buffer_log(message) def close(self): - self.close_flusher() - self.flush_sync() + # First gracefully shut down any threads that are still attempting + # to add log messages to the buffer. This ensures that we don't lose + # any log messages that are in the process of being added to the + # buffer. if self.worker_thread_pool: self.worker_thread_pool.shutdown(wait=True) self.worker_thread_pool = None + + # Now that we've shut down the worker threads, we can safely close + # the flusher thread. + self.close_flusher() + + # Manually force a flush of any remaining log messages in the buffer. + # We block here to ensure that the flush completes prior to the + # application exiting and because the probability of this + # introducing a noticeable delay is very low because close() is only + # called when the logger and application are shutting down. + self.schedule_flush_sync(should_block=True) + + # Finally, shut down the thread pool that was used to send the log + # messages to the server. We can assume at this point that all log + # messages that were in the buffer prior to the worker threads + # shutting down have been sent to the server. if self.request_thread_pool: self.request_thread_pool.shutdown(wait=True) self.request_thread_pool = None diff --git a/tests/test_logger.py b/tests/test_logger.py index 6ee1a08..e88b7ce 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -71,99 +71,100 @@ def shutdown(self, wait=True): class LogDNAHandlerTest(unittest.TestCase): + def setUp(self): + self.handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + + def tearDown(self): + self.handler.close() def test_handler(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - self.assertIsInstance(handler, logging.Handler) - self.assertIsInstance(handler.internal_handler, logging.StreamHandler) - self.assertIsNotNone(handler.internalLogger) - self.assertEqual(handler.key, LOGDNA_API_KEY) - self.assertEqual(handler.hostname, sample_options['hostname']) - self.assertEqual(handler.ip, sample_options['ip']) - self.assertEqual(handler.mac, sample_options['mac']) - self.assertEqual(handler.loglevel, 'info') - self.assertEqual(handler.app, '') - self.assertEqual(handler.env, '') - self.assertEqual(handler.tags, sample_options['tags'].split(',')) - self.assertEqual(handler.custom_fields, defaults['META_FIELDS']) + self.assertIsInstance(self.handler, logging.Handler) + self.assertIsInstance( + self.handler.internal_handler, logging.StreamHandler) + self.assertIsNotNone(self.handler.internalLogger) + self.assertEqual(self.handler.key, LOGDNA_API_KEY) + self.assertEqual(self.handler.hostname, sample_options['hostname']) + self.assertEqual(self.handler.ip, sample_options['ip']) + self.assertEqual(self.handler.mac, sample_options['mac']) + self.assertEqual(self.handler.loglevel, 'info') + self.assertEqual(self.handler.app, '') + self.assertEqual(self.handler.env, '') + self.assertEqual(self.handler.tags, sample_options['tags'].split(',')) + self.assertEqual(self.handler.custom_fields, defaults['META_FIELDS']) # Set the Connection Variables - self.assertEqual(handler.url, defaults['LOGDNA_URL']) - self.assertEqual(handler.request_timeout, + self.assertEqual(self.handler.url, defaults['LOGDNA_URL']) + self.assertEqual(self.handler.request_timeout, defaults['DEFAULT_REQUEST_TIMEOUT']) - self.assertEqual(handler.user_agent, defaults['USER_AGENT']) - self.assertEqual(handler.max_retry_attempts, + self.assertEqual(self.handler.user_agent, defaults['USER_AGENT']) + self.assertEqual(self.handler.max_retry_attempts, defaults['MAX_RETRY_ATTEMPTS']) - self.assertEqual(handler.max_retry_jitter, + self.assertEqual(self.handler.max_retry_jitter, defaults['MAX_RETRY_JITTER']) - self.assertEqual(handler.max_concurrent_requests, + self.assertEqual(self.handler.max_concurrent_requests, defaults['MAX_CONCURRENT_REQUESTS']) - self.assertEqual(handler.retry_interval_secs, + self.assertEqual(self.handler.retry_interval_secs, sample_options['retry_interval_secs']) # Set the Flush-related Variables - self.assertEqual(handler.buf, []) - self.assertEqual(handler.buf_size, 0) - self.assertEqual(handler.secondary, []) - self.assertFalse(handler.exception_flag) - self.assertIsNone(handler.flusher) - self.assertTrue(handler.index_meta) - self.assertEqual(handler.flush_limit, defaults['FLUSH_LIMIT']) - self.assertEqual(handler.flush_interval_secs, + self.assertEqual(self.handler.buf, []) + self.assertEqual(self.handler.buf_size, 0) + self.assertIsNotNone(self.handler.flusher) + self.assertTrue(self.handler.index_meta) + self.assertEqual(self.handler.flush_limit, defaults['FLUSH_LIMIT']) + self.assertEqual(self.handler.flush_interval_secs, defaults['FLUSH_INTERVAL_SECS']) - self.assertEqual(handler.buf_retention_limit, + self.assertEqual(self.handler.buf_retention_limit, defaults['BUF_RETENTION_LIMIT']) # Set up the Thread Pools - self.assertIsInstance(handler.worker_thread_pool, ThreadPoolExecutor) - self.assertIsInstance(handler.request_thread_pool, ThreadPoolExecutor) - self.assertEqual(handler.level, logging.DEBUG) + self.assertIsInstance( + self.handler.worker_thread_pool, ThreadPoolExecutor) + self.assertIsInstance( + self.handler.request_thread_pool, ThreadPoolExecutor) + self.assertEqual(self.handler.level, logging.DEBUG) def test_flusher(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - self.assertIsNone(handler.flusher) - handler.start_flusher() - self.assertIsNotNone(handler.flusher) - handler.close_flusher() - self.assertIsNone(handler.flusher) + self.assertIsNotNone(self.handler.flusher) + self.handler.close_flusher() + self.assertIsNone(self.handler.flusher) + self.assertTrue(self.handler.flusher_stopped) def test_emit(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - handler.buffer_log = unittest.mock.Mock() - handler.emit(sample_record) + self.handler.buffer_log = unittest.mock.Mock() + self.handler.emit(sample_record) sample_message['timestamp'] = unittest.mock.ANY - handler.buffer_log.assert_called_once_with(sample_message) + self.handler.buffer_log.assert_called_once_with(sample_message) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) - def test_try_request(self): + def test_try_lock_and_do_flush_request(self): with patch('requests.post') as post_mock: r = requests.Response() r.status_code = 200 r.reason = 'OK' post_mock.return_value = r - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() + self.handler.buf = [sample_message] + test_buf = self.handler.buf.copy() + self.handler.try_lock_and_do_flush_request() post_mock.assert_called_with( - url=handler.url, + url=self.handler.url, json={ 'e': 'ls', - 'ls': handler.buf + 'ls': test_buf }, - auth=('user', handler.key), + auth=('user', self.handler.key), params={ - 'hostname': handler.hostname, - 'ip': handler.ip, - 'mac': handler.mac, - 'tags': handler.tags, + 'hostname': self.handler.hostname, + 'ip': self.handler.ip, + 'mac': self.handler.mac, + 'tags': self.handler.tags, 'now': int(now * 1000) }, stream=True, allow_redirects=True, - timeout=handler.request_timeout, - headers={'user-agent': handler.user_agent}) - self.assertFalse(handler.exception_flag) + timeout=self.handler.request_timeout, + headers={'user-agent': self.handler.user_agent}) self.assertTrue(post_mock.call_count, 1) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) @@ -173,11 +174,9 @@ def test_try_request_500(self): r.status_code = 500 r.reason = 'Internal Server Error' post_mock.return_value = r - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - self.assertTrue(handler.exception_flag) + self.handler.buf = [sample_message] + self.handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) @@ -187,11 +186,9 @@ def test_try_request_502(self): r.status_code = 502 r.reason = 'Bad Gateway' post_mock.return_value = r - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - self.assertTrue(handler.exception_flag) + self.handler.buf = [sample_message] + self.handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) @@ -201,11 +198,9 @@ def test_try_request_504(self): r.status_code = 504 r.reason = 'Gateway Timeout' post_mock.return_value = r - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - self.assertTrue(handler.exception_flag) + self.handler.buf = [sample_message] + self.handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) @@ -215,11 +210,9 @@ def test_try_request_429(self): r.status_code = 429 r.reason = 'Too Many Requests' post_mock.return_value = r - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - self.assertTrue(handler.exception_flag) + self.handler.buf = [sample_message] + self.handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) @@ -229,11 +222,9 @@ def test_try_request_403(self): r.status_code = 403 r.reason = 'Forbidden' post_mock.return_value = r - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - self.assertFalse(handler.exception_flag) + self.handler.buf = [sample_message] + self.handler.try_request([]) self.assertTrue(post_mock.call_count, 1) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) @@ -244,64 +235,91 @@ def test_try_request_403_log_response(self): r.reason = 'Forbidden' post_mock.return_value = r sample_options['log_error_response'] = True - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) sample_message['timestamp'] = unittest.mock.ANY - handler.buf = [sample_message] - handler.try_request() - self.assertFalse(handler.exception_flag) + self.handler.buf = [sample_message] + self.handler.try_request([]) self.assertTrue(post_mock.call_count, 1) def test_close(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - handler.close_flusher = unittest.mock.Mock() - handler.flush_sync = unittest.mock.Mock() - handler.close() - handler.close_flusher.assert_called_once_with() - handler.flush_sync.assert_called_once_with() - self.assertIsNone(handler.worker_thread_pool) - self.assertIsNone(handler.request_thread_pool) + close_flusher_mock = unittest.mock.Mock() + close_flusher_mock.side_effect = self.handler.close_flusher + self.handler.close_flusher = close_flusher_mock + self.handler.schedule_flush_sync = unittest.mock.Mock() + self.handler.close() + self.handler.close_flusher.assert_called_once_with() + self.handler.schedule_flush_sync.assert_called_once_with( + should_block=True) + self.assertIsNone(self.handler.worker_thread_pool) + self.assertIsNone(self.handler.request_thread_pool) def test_flush(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - handler.worker_thread_pool = MockThreadPoolExecutor() - handler.request_thread_pool = MockThreadPoolExecutor() - handler.buf_size += 1 - handler.try_request = unittest.mock.Mock() - handler.flush() - handler.try_request.assert_called_once_with() - - def test_flush_secondary(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - clean = handler.clean_after_success - handler.worker_thread_pool = MockThreadPoolExecutor() - handler.request_thread_pool = MockThreadPoolExecutor() - handler.buf_size = 0 - handler.secondary.append(sample_message) - handler.send_request = unittest.mock.MagicMock(return_value=True) - handler.clean_after_success = unittest.mock.Mock() - self.assertEqual(handler.secondary, [sample_message]) - handler.flush() - handler.send_request.assert_called_with({ - 'e': 'ls', - 'ls': [sample_message] - }) - clean() - self.assertEqual(handler.secondary, []) - self.assertEqual(handler.buf, []) + self.handler.worker_thread_pool = MockThreadPoolExecutor() + self.handler.request_thread_pool = MockThreadPoolExecutor() + self.handler.buf = [sample_message] + self.handler.buf_size += len(self.handler.buf) + self.handler.try_request = unittest.mock.Mock() + self.handler.flush() + self.handler.try_request.assert_called_once_with([sample_message]) def test_buffer_log(self): - handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - handler.worker_thread_pool = MockThreadPoolExecutor() - handler.request_thread_pool = MockThreadPoolExecutor() - handler.close_flusher = unittest.mock.Mock() - handler.flush = unittest.mock.Mock() + self.handler.worker_thread_pool = MockThreadPoolExecutor() + self.handler.request_thread_pool = MockThreadPoolExecutor() + self.handler.flush = unittest.mock.Mock() sample_message['timestamp'] = unittest.mock.ANY - handler.flush_limit = 0 - handler.buffer_log(sample_message) - handler.close_flusher.assert_called_once_with() - handler.flush.assert_called_once_with() - self.assertEqual(handler.buf, [sample_message]) - self.assertEqual(handler.buf_size, len(sample_message['line'])) + self.handler.flush_limit = 0 + self.handler.buffer_log(sample_message) + self.handler.flush.assert_called_once_with() + self.assertEqual(self.handler.buf, [sample_message]) + self.assertEqual(self.handler.buf_size, len(sample_message['line'])) + + # Attempts to reproduce the specific scenario that resulted in + # https://mezmo.atlassian.net/browse/LOG-15414 where log messages + # would be dropped due to race conditions. The test essentially + # does the following: + # 1. Create a LogDNAHandler + # 2. Call handler.emit() with a large number of log records at a rate + # sufficiently high to trigger the race + # 3. Verify that no log records are dropped. + # + # This test is not deterministic, but it should be sufficient to + # catch regressions. It reliably reproduces the issue in question + # and fails with the previous version of this code. + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) + def test_when_emitManyLogs_then_noLogsDropped(self): + num_logs = 10**5 + received = list() + + def append_received(json=None, **kwargs): + ids = [int(log['line']) for log in json['ls']] + for id in ids: + received.append(id) + r = requests.Response() + r.status_code = 200 + r.reason = 'OK' + # Simulate some reasonable request latency + time.sleep(0.1) + return r + + def get_sample_record(id): + return logging.LogRecord( + name='test', + level=logging.INFO, + pathname='test', + lineno=5, + msg=str(id), + args=[sample_args], + exc_info='', + func='', + sinfo='') + + with patch('requests.post', side_effect=append_received): + + for i in range(num_logs): + self.handler.emit(get_sample_record(i)) + self.handler.close() + + self.assertEqual(len(received), num_logs) + self.assertEquals(set(received), set(range(num_logs))) if __name__ == '__main__': From 913e5f4f35f2920a6b2162022b93495b4774654c Mon Sep 17 00:00:00 2001 From: Stephen Hawkins <139154814+shawkinsmezmo@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:24:42 -0700 Subject: [PATCH 130/139] fix: Bump semver (#101) Co-authored-by: Stephen Hawkins --- tests/test_logger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_logger.py b/tests/test_logger.py index e88b7ce..7fc0564 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -313,7 +313,6 @@ def get_sample_record(id): sinfo='') with patch('requests.post', side_effect=append_received): - for i in range(num_logs): self.handler.emit(get_sample_record(i)) self.handler.close() From 658f6c5c9933295aaec940a7c57a6b7a5209405b Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Tue, 18 Jul 2023 17:29:34 +0000 Subject: [PATCH 131/139] release: Version 1.18.8 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbec101..4b335ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.8 (2023-07-18) +### Fix +* Bump semver ([#101](https://github.com/logdna/python/issues/101)) ([`913e5f4`](https://github.com/logdna/python/commit/913e5f4f35f2920a6b2162022b93495b4774654c)) + ## v1.18.7 (2023-05-05) ### Fix * Gate buils from non maintainers ([`97803b5`](https://github.com/logdna/python/commit/97803b55a102539a75b0763891c1d27757460067)) diff --git a/logdna/VERSION b/logdna/VERSION index d6f3a38..1a31d39 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.7 +1.18.8 diff --git a/pyproject.toml b/pyproject.toml index 188319d..d62d663 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.7" +version = "1.18.8" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 17a69b044de43a7a9d7d3e6eb65a0c60f1fa23f0 Mon Sep 17 00:00:00 2001 From: Stephen Hawkins <139154814+shawkinsmezmo@users.noreply.github.com> Date: Thu, 20 Jul 2023 22:33:10 -0700 Subject: [PATCH 132/139] fix: Make flush thread a daemon thread to prevent shutdown hang (#102) Co-authored-by: Stephen Hawkins --- logdna/logdna.py | 6 +++--- tests/test_logger.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 3a5043a..36aa9e2 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -78,8 +78,8 @@ def __init__(self, key, options={}): # Start the flusher self.flusher_stopped = threading.Event() - self.flusher = threading.Timer( - self.flush_interval_secs, self.flush_timer_worker) + self.flusher = threading.Thread( + target=self.flush_timer_worker, daemon=True) self.flusher.start() def flush_timer_worker(self): @@ -93,7 +93,7 @@ def flush_timer_worker(self): def close_flusher(self): if self.flusher: self.flusher_stopped.set() - self.flusher.cancel() + self.flusher.join() self.flusher = None def buffer_log(self, message): diff --git a/tests/test_logger.py b/tests/test_logger.py index 7fc0564..f195555 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -320,6 +320,11 @@ def get_sample_record(id): self.assertEqual(len(received), num_logs) self.assertEquals(set(received), set(range(num_logs))) + def test_when_handlerShutDown_then_handlerDoesNotHang(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + self.assertIsNotNone(handler) + # Do nothing. This test should pass by virtue of not hanging. + if __name__ == '__main__': unittest.main() From ceb3545171aa6d44c7d5fa2e1fe90012cae5650a Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Fri, 21 Jul 2023 05:37:03 +0000 Subject: [PATCH 133/139] release: Version 1.18.9 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b335ed..087d85c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.9 (2023-07-21) +### Fix +* Make flush thread a daemon thread to prevent shutdown hang ([#102](https://github.com/logdna/python/issues/102)) ([`17a69b0`](https://github.com/logdna/python/commit/17a69b044de43a7a9d7d3e6eb65a0c60f1fa23f0)) + ## v1.18.8 (2023-07-18) ### Fix * Bump semver ([#101](https://github.com/logdna/python/issues/101)) ([`913e5f4`](https://github.com/logdna/python/commit/913e5f4f35f2920a6b2162022b93495b4774654c)) diff --git a/logdna/VERSION b/logdna/VERSION index 1a31d39..cafc0b7 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.8 +1.18.9 diff --git a/pyproject.toml b/pyproject.toml index d62d663..9e67a25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.8" +version = "1.18.9" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 5394e9714779878cd415a4566cd44d9183e150b9 Mon Sep 17 00:00:00 2001 From: Stephen Hawkins <139154814+shawkinsmezmo@users.noreply.github.com> Date: Wed, 26 Jul 2023 08:59:51 -0700 Subject: [PATCH 134/139] fix: Utilize apikey header for auth to make compatible with pipelines (#104) Co-authored-by: Stephen Hawkins --- logdna/logdna.py | 7 +++++-- tests/test_logger.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 36aa9e2..fd55074 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -180,9 +180,12 @@ def send_request(self, data): # noqa: max-complexity: 13 False - retry, keep flush buffer """ try: + headers = { + 'user-agent': self.user_agent, + 'apikey': self.key + } response = requests.post(url=self.url, json=data, - auth=('user', self.key), params={ 'hostname': self.hostname, 'ip': self.ip, @@ -193,7 +196,7 @@ def send_request(self, data): # noqa: max-complexity: 13 stream=True, allow_redirects=True, timeout=self.request_timeout, - headers={'user-agent': self.user_agent}) + headers=headers) status_code = response.status_code ''' diff --git a/tests/test_logger.py b/tests/test_logger.py index f195555..8adacda 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -153,7 +153,6 @@ def test_try_lock_and_do_flush_request(self): 'e': 'ls', 'ls': test_buf }, - auth=('user', self.handler.key), params={ 'hostname': self.handler.hostname, 'ip': self.handler.ip, @@ -164,7 +163,9 @@ def test_try_lock_and_do_flush_request(self): stream=True, allow_redirects=True, timeout=self.handler.request_timeout, - headers={'user-agent': self.handler.user_agent}) + headers={ + 'user-agent': self.handler.user_agent, + 'apikey': LOGDNA_API_KEY}) self.assertTrue(post_mock.call_count, 1) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) From 8af0c6dcee19037a782310861e267e4d8926082b Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Wed, 26 Jul 2023 16:04:15 +0000 Subject: [PATCH 135/139] release: Version 1.18.10 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 087d85c..44a7ab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.10 (2023-07-26) +### Fix +* Utilize apikey header for auth to make compatible with pipelines ([#104](https://github.com/logdna/python/issues/104)) ([`5394e97`](https://github.com/logdna/python/commit/5394e9714779878cd415a4566cd44d9183e150b9)) + ## v1.18.9 (2023-07-21) ### Fix * Make flush thread a daemon thread to prevent shutdown hang ([#102](https://github.com/logdna/python/issues/102)) ([`17a69b0`](https://github.com/logdna/python/commit/17a69b044de43a7a9d7d3e6eb65a0c60f1fa23f0)) diff --git a/logdna/VERSION b/logdna/VERSION index cafc0b7..0150af1 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.9 +1.18.10 diff --git a/pyproject.toml b/pyproject.toml index 9e67a25..9c28b2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.9" +version = "1.18.10" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 0337a09433953227dabb0b65da8583e8f9273986 Mon Sep 17 00:00:00 2001 From: Stephen Hawkins <139154814+shawkinsmezmo@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:20:32 -0700 Subject: [PATCH 136/139] fix: Remove Thread/event to fix Django regression (#107) Co-authored-by: Stephen Hawkins --- logdna/logdna.py | 47 +++++----- tests/test_logger.py | 201 +++++++++++++++++++++++-------------------- 2 files changed, 132 insertions(+), 116 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index fd55074..6022c07 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -76,24 +76,17 @@ def __init__(self, key, options={}): self.setLevel(logging.DEBUG) self.lock = threading.RLock() - # Start the flusher - self.flusher_stopped = threading.Event() - self.flusher = threading.Thread( - target=self.flush_timer_worker, daemon=True) - self.flusher.start() - - def flush_timer_worker(self): - while not self.flusher_stopped.wait(self.flush_interval_secs): - try: - self.flush() - except Exception as e: - self.internalLogger.exception( - f'Error in flush_timer_worker: {e}') + self.flusher = None + + def start_flusher(self): + if not self.flusher: + self.flusher = threading.Timer( + self.flush_interval_secs, self.flush) + self.flusher.start() def close_flusher(self): if self.flusher: - self.flusher_stopped.set() - self.flusher.join() + self.flusher.cancel() self.flusher = None def buffer_log(self, message): @@ -119,7 +112,10 @@ def buffer_log_sync(self, message): self.buf_retention_limit) if self.buf_size >= self.flush_limit: + self.close_flusher() self.flush() + else: + self.start_flusher() except Exception as e: self.internalLogger.exception(f'Error in buffer_log_sync: {e}') finally: @@ -143,13 +139,15 @@ def try_lock_and_do_flush_request(self, should_block=False): local_buf = [] if self.lock.acquire(blocking=should_block): if not self.buf: + self.close_flusher() self.lock.release() return - if self.buf: - local_buf = self.buf.copy() - self.buf.clear() - self.buf_size = 0 + local_buf = self.buf.copy() + self.buf.clear() + self.buf_size = 0 + if local_buf: + self.close_flusher() self.lock.release() if local_buf: @@ -304,10 +302,10 @@ def emit(self, record): 'line': msg, 'level': record['levelname'] or self.loglevel, 'app': self.app or record['module'], - 'env': self.env + 'env': self.env, + 'meta': {} } - message['meta'] = {} for key in self.custom_fields: if key in record: if isinstance(record[key], tuple): @@ -328,6 +326,9 @@ def emit(self, record): self.buffer_log(message) def close(self): + # Close the flusher + self.close_flusher() + # First gracefully shut down any threads that are still attempting # to add log messages to the buffer. This ensures that we don't lose # any log messages that are in the process of being added to the @@ -336,10 +337,6 @@ def close(self): self.worker_thread_pool.shutdown(wait=True) self.worker_thread_pool = None - # Now that we've shut down the worker threads, we can safely close - # the flusher thread. - self.close_flusher() - # Manually force a flush of any remaining log messages in the buffer. # We block here to ensure that the flush completes prior to the # application exiting and because the probability of this diff --git a/tests/test_logger.py b/tests/test_logger.py index 8adacda..6fe68e3 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -71,207 +71,225 @@ def shutdown(self, wait=True): class LogDNAHandlerTest(unittest.TestCase): - def setUp(self): - self.handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) - - def tearDown(self): - self.handler.close() - def test_handler(self): - self.assertIsInstance(self.handler, logging.Handler) + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + self.assertIsInstance(handler, logging.Handler) self.assertIsInstance( - self.handler.internal_handler, logging.StreamHandler) - self.assertIsNotNone(self.handler.internalLogger) - self.assertEqual(self.handler.key, LOGDNA_API_KEY) - self.assertEqual(self.handler.hostname, sample_options['hostname']) - self.assertEqual(self.handler.ip, sample_options['ip']) - self.assertEqual(self.handler.mac, sample_options['mac']) - self.assertEqual(self.handler.loglevel, 'info') - self.assertEqual(self.handler.app, '') - self.assertEqual(self.handler.env, '') - self.assertEqual(self.handler.tags, sample_options['tags'].split(',')) - self.assertEqual(self.handler.custom_fields, defaults['META_FIELDS']) + handler.internal_handler, logging.StreamHandler) + self.assertIsNotNone(handler.internalLogger) + self.assertEqual(handler.key, LOGDNA_API_KEY) + self.assertEqual(handler.hostname, sample_options['hostname']) + self.assertEqual(handler.ip, sample_options['ip']) + self.assertEqual(handler.mac, sample_options['mac']) + self.assertEqual(handler.loglevel, 'info') + self.assertEqual(handler.app, '') + self.assertEqual(handler.env, '') + self.assertEqual(handler.tags, sample_options['tags'].split(',')) + self.assertEqual(handler.custom_fields, defaults['META_FIELDS']) # Set the Connection Variables - self.assertEqual(self.handler.url, defaults['LOGDNA_URL']) - self.assertEqual(self.handler.request_timeout, + self.assertEqual(handler.url, defaults['LOGDNA_URL']) + self.assertEqual(handler.request_timeout, defaults['DEFAULT_REQUEST_TIMEOUT']) - self.assertEqual(self.handler.user_agent, defaults['USER_AGENT']) - self.assertEqual(self.handler.max_retry_attempts, + self.assertEqual(handler.user_agent, defaults['USER_AGENT']) + self.assertEqual(handler.max_retry_attempts, defaults['MAX_RETRY_ATTEMPTS']) - self.assertEqual(self.handler.max_retry_jitter, + self.assertEqual(handler.max_retry_jitter, defaults['MAX_RETRY_JITTER']) - self.assertEqual(self.handler.max_concurrent_requests, + self.assertEqual(handler.max_concurrent_requests, defaults['MAX_CONCURRENT_REQUESTS']) - self.assertEqual(self.handler.retry_interval_secs, + self.assertEqual(handler.retry_interval_secs, sample_options['retry_interval_secs']) # Set the Flush-related Variables - self.assertEqual(self.handler.buf, []) - self.assertEqual(self.handler.buf_size, 0) - self.assertIsNotNone(self.handler.flusher) - self.assertTrue(self.handler.index_meta) - self.assertEqual(self.handler.flush_limit, defaults['FLUSH_LIMIT']) - self.assertEqual(self.handler.flush_interval_secs, + self.assertEqual(handler.buf, []) + self.assertEqual(handler.buf_size, 0) + self.assertIsNone(handler.flusher) + self.assertTrue(handler.index_meta) + self.assertEqual(handler.flush_limit, defaults['FLUSH_LIMIT']) + self.assertEqual(handler.flush_interval_secs, defaults['FLUSH_INTERVAL_SECS']) - self.assertEqual(self.handler.buf_retention_limit, + self.assertEqual(handler.buf_retention_limit, defaults['BUF_RETENTION_LIMIT']) # Set up the Thread Pools self.assertIsInstance( - self.handler.worker_thread_pool, ThreadPoolExecutor) + handler.worker_thread_pool, ThreadPoolExecutor) self.assertIsInstance( - self.handler.request_thread_pool, ThreadPoolExecutor) - self.assertEqual(self.handler.level, logging.DEBUG) + handler.request_thread_pool, ThreadPoolExecutor) + self.assertEqual(handler.level, logging.DEBUG) + @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_flusher(self): - self.assertIsNotNone(self.handler.flusher) - self.handler.close_flusher() - self.assertIsNone(self.handler.flusher) - self.assertTrue(self.handler.flusher_stopped) + with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + r = requests.Response() + r.status_code = 200 + r.reason = 'OK' + post_mock.return_value = r + handler.emit(sample_record) + self.assertIsNotNone(handler.flusher) + handler.close_flusher() + self.assertIsNone(handler.flusher) def test_emit(self): - self.handler.buffer_log = unittest.mock.Mock() - self.handler.emit(sample_record) + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + handler.buffer_log = unittest.mock.Mock() + handler.emit(sample_record) sample_message['timestamp'] = unittest.mock.ANY - self.handler.buffer_log.assert_called_once_with(sample_message) + handler.buffer_log.assert_called_once_with(sample_message) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_lock_and_do_flush_request(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 200 r.reason = 'OK' post_mock.return_value = r sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - test_buf = self.handler.buf.copy() - self.handler.try_lock_and_do_flush_request() + handler.buf = [sample_message] + test_buf = handler.buf.copy() + handler.try_lock_and_do_flush_request() post_mock.assert_called_with( - url=self.handler.url, + url=handler.url, json={ 'e': 'ls', 'ls': test_buf }, params={ - 'hostname': self.handler.hostname, - 'ip': self.handler.ip, - 'mac': self.handler.mac, - 'tags': self.handler.tags, + 'hostname': handler.hostname, + 'ip': handler.ip, + 'mac': handler.mac, + 'tags': handler.tags, 'now': int(now * 1000) }, stream=True, allow_redirects=True, - timeout=self.handler.request_timeout, + timeout=handler.request_timeout, headers={ - 'user-agent': self.handler.user_agent, + 'user-agent': handler.user_agent, 'apikey': LOGDNA_API_KEY}) self.assertTrue(post_mock.call_count, 1) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_request_500(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 500 r.reason = 'Internal Server Error' post_mock.return_value = r sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - self.handler.try_request([]) + handler.buf = [sample_message] + handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_request_502(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 502 r.reason = 'Bad Gateway' post_mock.return_value = r sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - self.handler.try_request([]) + handler.buf = [sample_message] + handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_request_504(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 504 r.reason = 'Gateway Timeout' post_mock.return_value = r sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - self.handler.try_request([]) + handler.buf = [sample_message] + handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_request_429(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 429 r.reason = 'Too Many Requests' post_mock.return_value = r sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - self.handler.try_request([]) + handler.buf = [sample_message] + handler.try_request([]) self.assertTrue(post_mock.call_count, 3) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_request_403(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 403 r.reason = 'Forbidden' post_mock.return_value = r sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - self.handler.try_request([]) + handler.buf = [sample_message] + handler.try_request([]) self.assertTrue(post_mock.call_count, 1) @mock.patch('time.time', unittest.mock.MagicMock(return_value=now)) def test_try_request_403_log_response(self): with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) r = requests.Response() r.status_code = 403 r.reason = 'Forbidden' post_mock.return_value = r sample_options['log_error_response'] = True sample_message['timestamp'] = unittest.mock.ANY - self.handler.buf = [sample_message] - self.handler.try_request([]) + handler.buf = [sample_message] + handler.try_request([]) self.assertTrue(post_mock.call_count, 1) def test_close(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) close_flusher_mock = unittest.mock.Mock() - close_flusher_mock.side_effect = self.handler.close_flusher - self.handler.close_flusher = close_flusher_mock - self.handler.schedule_flush_sync = unittest.mock.Mock() - self.handler.close() - self.handler.close_flusher.assert_called_once_with() - self.handler.schedule_flush_sync.assert_called_once_with( + close_flusher_mock.side_effect = handler.close_flusher + handler.close_flusher = close_flusher_mock + handler.schedule_flush_sync = unittest.mock.Mock() + handler.close() + handler.close_flusher.assert_called_once_with() + handler.schedule_flush_sync.assert_called_once_with( should_block=True) - self.assertIsNone(self.handler.worker_thread_pool) - self.assertIsNone(self.handler.request_thread_pool) + self.assertIsNone(handler.worker_thread_pool) + self.assertIsNone(handler.request_thread_pool) def test_flush(self): - self.handler.worker_thread_pool = MockThreadPoolExecutor() - self.handler.request_thread_pool = MockThreadPoolExecutor() - self.handler.buf = [sample_message] - self.handler.buf_size += len(self.handler.buf) - self.handler.try_request = unittest.mock.Mock() - self.handler.flush() - self.handler.try_request.assert_called_once_with([sample_message]) + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + handler.worker_thread_pool = MockThreadPoolExecutor() + handler.request_thread_pool = MockThreadPoolExecutor() + handler.buf = [sample_message] + handler.buf_size += len(handler.buf) + handler.try_request = unittest.mock.Mock() + handler.flush() + handler.try_request.assert_called_once_with([sample_message]) def test_buffer_log(self): - self.handler.worker_thread_pool = MockThreadPoolExecutor() - self.handler.request_thread_pool = MockThreadPoolExecutor() - self.handler.flush = unittest.mock.Mock() - sample_message['timestamp'] = unittest.mock.ANY - self.handler.flush_limit = 0 - self.handler.buffer_log(sample_message) - self.handler.flush.assert_called_once_with() - self.assertEqual(self.handler.buf, [sample_message]) - self.assertEqual(self.handler.buf_size, len(sample_message['line'])) + with patch('requests.post') as post_mock: + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + r = requests.Response() + r.status_code = 200 + r.reason = 'OK' + post_mock.return_value = r + handler.worker_thread_pool = MockThreadPoolExecutor() + handler.request_thread_pool = MockThreadPoolExecutor() + handler.flush = unittest.mock.Mock() + sample_message['timestamp'] = now + handler.flush_limit = 0 + handler.buffer_log(sample_message) + handler.flush.assert_called_once_with() + self.assertEqual(handler.buf, [sample_message]) + self.assertEqual(handler.buf_size, len(sample_message['line'])) # Attempts to reproduce the specific scenario that resulted in # https://mezmo.atlassian.net/browse/LOG-15414 where log messages @@ -314,12 +332,13 @@ def get_sample_record(id): sinfo='') with patch('requests.post', side_effect=append_received): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) for i in range(num_logs): - self.handler.emit(get_sample_record(i)) - self.handler.close() + handler.emit(get_sample_record(i)) + handler.close() self.assertEqual(len(received), num_logs) - self.assertEquals(set(received), set(range(num_logs))) + self.assertEqual(set(received), set(range(num_logs))) def test_when_handlerShutDown_then_handlerDoesNotHang(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) From b2af94824823d72caaba66de6118b1c34c64deea Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Wed, 26 Jul 2023 16:22:38 +0000 Subject: [PATCH 137/139] release: Version 1.18.11 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a7ab8..13e061d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.11 (2023-07-26) +### Fix +* Remove Thread/event to fix Django regression ([#107](https://github.com/logdna/python/issues/107)) ([`0337a09`](https://github.com/logdna/python/commit/0337a09433953227dabb0b65da8583e8f9273986)) + ## v1.18.10 (2023-07-26) ### Fix * Utilize apikey header for auth to make compatible with pipelines ([#104](https://github.com/logdna/python/issues/104)) ([`5394e97`](https://github.com/logdna/python/commit/5394e9714779878cd415a4566cd44d9183e150b9)) diff --git a/logdna/VERSION b/logdna/VERSION index 0150af1..6961fed 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.10 +1.18.11 diff --git a/pyproject.toml b/pyproject.toml index 9c28b2e..c84222e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.10" +version = "1.18.11" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT" From 5b04d72b42686d926fb5e73dadb2d7a1ba16d9c3 Mon Sep 17 00:00:00 2001 From: Stephen Hawkins <139154814+shawkinsmezmo@users.noreply.github.com> Date: Thu, 27 Jul 2023 14:37:44 -0700 Subject: [PATCH 138/139] fix: Don't overwrite base logging Handler class lock var (#108) Co-authored-by: Stephen Hawkins --- logdna/logdna.py | 13 +++++++------ tests/test_logger.py | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/logdna/logdna.py b/logdna/logdna.py index 6022c07..e9f6128 100644 --- a/logdna/logdna.py +++ b/logdna/logdna.py @@ -74,7 +74,7 @@ def __init__(self, key, options={}): max_workers=self.max_concurrent_requests) self.setLevel(logging.DEBUG) - self.lock = threading.RLock() + self._lock = threading.RLock() self.flusher = None @@ -100,7 +100,7 @@ def buffer_log(self, message): def buffer_log_sync(self, message): # Attempt to acquire lock to write to buffer - if self.lock.acquire(blocking=True): + if self._lock.acquire(blocking=True): try: msglen = len(message['line']) if self.buf_size + msglen < self.buf_retention_limit: @@ -119,7 +119,7 @@ def buffer_log_sync(self, message): except Exception as e: self.internalLogger.exception(f'Error in buffer_log_sync: {e}') finally: - self.lock.release() + self._lock.release() def flush(self): self.schedule_flush_sync() @@ -137,10 +137,10 @@ def schedule_flush_sync(self, should_block=False): def try_lock_and_do_flush_request(self, should_block=False): local_buf = [] - if self.lock.acquire(blocking=should_block): + if self._lock.acquire(blocking=should_block): if not self.buf: self.close_flusher() - self.lock.release() + self._lock.release() return local_buf = self.buf.copy() @@ -148,7 +148,7 @@ def try_lock_and_do_flush_request(self, should_block=False): self.buf_size = 0 if local_buf: self.close_flusher() - self.lock.release() + self._lock.release() if local_buf: self.try_request(local_buf) @@ -351,4 +351,5 @@ def close(self): if self.request_thread_pool: self.request_thread_pool.shutdown(wait=True) self.request_thread_pool = None + logging.Handler.close(self) diff --git a/tests/test_logger.py b/tests/test_logger.py index 6fe68e3..0a610c2 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -255,8 +255,8 @@ def test_close(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) close_flusher_mock = unittest.mock.Mock() close_flusher_mock.side_effect = handler.close_flusher - handler.close_flusher = close_flusher_mock handler.schedule_flush_sync = unittest.mock.Mock() + handler.close_flusher = close_flusher_mock handler.close() handler.close_flusher.assert_called_once_with() handler.schedule_flush_sync.assert_called_once_with( @@ -264,6 +264,19 @@ def test_close(self): self.assertIsNone(handler.worker_thread_pool) self.assertIsNone(handler.request_thread_pool) + # These should be separate objects, since there is already + # a variable in the base class named self.lock. We want + # to make sure that a separate lock is created for the + # locking semantics of the LogDNA Handler + def test_lock_var_separate_from_local_lock_var(self): + handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) + self.assertIsNotNone(handler) + + # Test that we did not replace the base class' instance var. + self.assertIsNotNone(handler._lock) + self.assertIsNotNone(handler.lock) + self.assertNotEquals(handler.lock, handler._lock) + def test_flush(self): handler = LogDNAHandler(LOGDNA_API_KEY, sample_options) handler.worker_thread_pool = MockThreadPoolExecutor() From 4968bca3f57b6a57af9b64745d6ecb4c1c771a97 Mon Sep 17 00:00:00 2001 From: LogDNA Bot Date: Thu, 27 Jul 2023 21:39:21 +0000 Subject: [PATCH 139/139] release: Version 1.18.12 [skip ci] Automatically generated by python-semantic-release --- CHANGELOG.md | 4 ++++ logdna/VERSION | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e061d..b52389a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +## v1.18.12 (2023-07-27) +### Fix +* Don't overwrite base logging Handler class lock var ([#108](https://github.com/logdna/python/issues/108)) ([`5b04d72`](https://github.com/logdna/python/commit/5b04d72b42686d926fb5e73dadb2d7a1ba16d9c3)) + ## v1.18.11 (2023-07-26) ### Fix * Remove Thread/event to fix Django regression ([#107](https://github.com/logdna/python/issues/107)) ([`0337a09`](https://github.com/logdna/python/commit/0337a09433953227dabb0b65da8583e8f9273986)) diff --git a/logdna/VERSION b/logdna/VERSION index 6961fed..43bae12 100644 --- a/logdna/VERSION +++ b/logdna/VERSION @@ -1 +1 @@ -1.18.11 +1.18.12 diff --git a/pyproject.toml b/pyproject.toml index c84222e..241a341 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "logdna" -version = "1.18.11" +version = "1.18.12" description = 'A Python Package for Sending Logs to LogDNA' authors = ["logdna "] license = "MIT"