commit python-Logbook for openSUSE:Factory

Hello community, here is the log from the commit of package python-Logbook for openSUSE:Factory checked in at 2019-09-30 15:54:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Logbook (Old) and /work/SRC/openSUSE:Factory/.python-Logbook.new.2352 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-Logbook" Mon Sep 30 15:54:40 2019 rev:5 rq:732888 version:1.5.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Logbook/python-Logbook.changes 2019-09-23 12:17:31.241805673 +0200 +++ /work/SRC/openSUSE:Factory/.python-Logbook.new.2352/python-Logbook.changes 2019-09-30 15:54:50.397944588 +0200 @@ -1,0 +2,6 @@ +Tue Sep 24 09:17:59 UTC 2019 - Tomáš Chvátal <tchvatal@suse.com> + +- Update to 1.5.2: + * Added support for asyncio and contextvars + +------------------------------------------------------------------- Old: ---- Logbook-1.4.3.tar.gz New: ---- Logbook-1.5.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Logbook.spec ++++++ --- /var/tmp/diff_new_pack.wKJUYU/_old 2019-09-30 15:54:53.249936998 +0200 +++ /var/tmp/diff_new_pack.wKJUYU/_new 2019-09-30 15:54:53.257936976 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-Logbook -Version: 1.4.3 +Version: 1.5.2 Release: 0 Summary: A logging replacement for Python License: BSD-3-Clause @@ -33,11 +33,12 @@ BuildRequires: %{python_module execnet >= 1.0.9} BuildRequires: %{python_module gevent} BuildRequires: %{python_module pip} +BuildRequires: %{python_module pytest >= 4.0} BuildRequires: %{python_module pytest-cov} -BuildRequires: %{python_module pytest} BuildRequires: %{python_module pyzmq} BuildRequires: %{python_module redis} BuildRequires: %{python_module setuptools} +BuildRequires: dos2unix BuildRequires: fdupes BuildRequires: python-mock BuildRequires: python-rpm-macros @@ -56,7 +57,7 @@ %prep %setup -q -n Logbook-%{version} -sed -i 's/\r$//' LICENSE +dos2unix LICENSE %build export CFLAGS="%{optflags} -fno-strict-aliasing" @@ -72,8 +73,9 @@ %check export CFLAGS="%{optflags}" %{_sbindir}/redis-server & -%python_exec -m pytest -kill %1 +# test_asyncio_context_management seems to fail in OBS +%pytest -k 'not test_asyncio_context_management' +kill %%1 %files %{python_files} %license LICENSE ++++++ Logbook-1.4.3.tar.gz -> Logbook-1.5.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/CHANGES new/Logbook-1.5.2/CHANGES --- old/Logbook-1.4.3/CHANGES 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/CHANGES 2019-08-21 06:33:30.000000000 +0200 @@ -1,6 +1,13 @@ Logbook Changelog ================= +Version 1.5.1 +------------- + +Released on August 20th, 2019 + +- Added support for asyncio and contextvars + Version 1.4.3 ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/Logbook.egg-info/PKG-INFO new/Logbook-1.5.2/Logbook.egg-info/PKG-INFO --- old/Logbook-1.4.3/Logbook.egg-info/PKG-INFO 2019-01-16 21:35:24.000000000 +0100 +++ new/Logbook-1.5.2/Logbook.egg-info/PKG-INFO 2019-08-21 06:34:20.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Logbook -Version: 1.4.3 +Version: 1.5.2 Summary: A logging replacement for Python Home-page: http://logbook.pocoo.org/ Author: Armin Ronacher, Georg Brandl @@ -64,12 +64,12 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Provides-Extra: dev Provides-Extra: test -Provides-Extra: jinja Provides-Extra: zmq -Provides-Extra: redis Provides-Extra: execnet -Provides-Extra: all Provides-Extra: compression +Provides-Extra: jinja +Provides-Extra: redis Provides-Extra: sqlalchemy -Provides-Extra: dev +Provides-Extra: all diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/Logbook.egg-info/SOURCES.txt new/Logbook-1.5.2/Logbook.egg-info/SOURCES.txt --- old/Logbook-1.4.3/Logbook.egg-info/SOURCES.txt 2019-01-16 21:35:24.000000000 +0100 +++ new/Logbook-1.5.2/Logbook.egg-info/SOURCES.txt 2019-08-21 06:34:20.000000000 +0200 @@ -30,6 +30,7 @@ scripts/test_setup.py tests/__init__.py tests/conftest.py +tests/test_asyncio.py tests/test_ci.py tests/test_deadlock.py tests/test_file_handler.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/Logbook.egg-info/requires.txt new/Logbook-1.5.2/Logbook.egg-info/requires.txt --- old/Logbook-1.4.3/Logbook.egg-info/requires.txt 2019-01-16 21:35:24.000000000 +0100 +++ new/Logbook-1.5.2/Logbook.egg-info/requires.txt 2019-08-21 06:34:20.000000000 +0200 @@ -1,14 +1,14 @@ [all] -pytest>4.0 -execnet>=1.0.9 -Jinja2 brotli -redis -cython pytest-cov>=2.6 +cython pyzmq +redis +execnet>=1.0.9 sqlalchemy +Jinja2 +pytest>4.0 [compression] brotli diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/PKG-INFO new/Logbook-1.5.2/PKG-INFO --- old/Logbook-1.4.3/PKG-INFO 2019-01-16 21:35:25.000000000 +0100 +++ new/Logbook-1.5.2/PKG-INFO 2019-08-21 06:34:20.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Logbook -Version: 1.4.3 +Version: 1.5.2 Summary: A logging replacement for Python Home-page: http://logbook.pocoo.org/ Author: Armin Ronacher, Georg Brandl @@ -64,12 +64,12 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Provides-Extra: dev Provides-Extra: test -Provides-Extra: jinja Provides-Extra: zmq -Provides-Extra: redis Provides-Extra: execnet -Provides-Extra: all Provides-Extra: compression +Provides-Extra: jinja +Provides-Extra: redis Provides-Extra: sqlalchemy -Provides-Extra: dev +Provides-Extra: all diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/README.md new/Logbook-1.5.2/README.md --- old/Logbook-1.4.3/README.md 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/README.md 2019-08-21 06:33:30.000000000 +0200 @@ -22,10 +22,10 @@ [ti]: https://secure.travis-ci.org/getlogbook/logbook.svg?branch=master [tl]: https://travis-ci.org/getlogbook/logbook [ai]: https://ci.appveyor.com/api/projects/status/quu99exa26e06npp?svg=true -[vi]: https://img.shields.io/badge/python-2.6%2C2.7%2C3.3%2C3.4%2C3.5-green.svg +[vi]: https://img.shields.io/badge/python-2.7%2C3.5%2C3.6%2C3.7-green.svg [di]: https://img.shields.io/pypi/dm/logbook.svg [al]: https://ci.appveyor.com/project/vmalloc/logbook [pi]: https://img.shields.io/pypi/v/logbook.svg -[pl]: https://pypi.python.org/pypi/Logbook +[pl]: https://pypi.org/pypi/Logbook [ci]: https://coveralls.io/repos/getlogbook/logbook/badge.svg?branch=master&servic... [cl]: https://coveralls.io/github/getlogbook/logbook?branch=master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/__version__.py new/Logbook-1.5.2/logbook/__version__.py --- old/Logbook-1.4.3/logbook/__version__.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/__version__.py 2019-08-21 06:33:30.000000000 +0200 @@ -1 +1 @@ -__version__ = "1.4.3" +__version__ = "1.5.2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/_fallback.py new/Logbook-1.5.2/logbook/_fallback.py --- old/Logbook-1.4.3/logbook/_fallback.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/_fallback.py 2019-08-21 06:33:30.000000000 +0200 @@ -12,7 +12,8 @@ from logbook.helpers import get_iterator_next_method from logbook.concurrency import ( thread_get_ident, greenlet_get_ident, thread_local, greenlet_local, - ThreadLock, GreenletRLock, is_gevent_enabled) + ThreadLock, GreenletRLock, is_gevent_enabled, ContextVar, context_get_ident, + is_context_enabled) _missing = object() _MAX_CONTEXT_OBJECT_CACHE = 256 @@ -67,6 +68,14 @@ """Pops the stacked object from the greenlet stack.""" raise NotImplementedError() + def push_context(self): + """Pushes the stacked object to the context stack.""" + raise NotImplementedError() + + def pop_context(self): + """Pops the stacked object from the context stack.""" + raise NotImplementedError() + def push_thread(self): """Pushes the stacked object to the thread stack.""" raise NotImplementedError() @@ -102,6 +111,13 @@ """ return _cls(self, self.push_greenlet, self.pop_greenlet) + def contextbound(self, _cls=_StackBound): + """Can be used in combination with the `with` statement to + execute code while the object is bound to the concurrent + context. + """ + return _cls(self, self.push_context, self.pop_context) + def threadbound(self, _cls=_StackBound): """Can be used in combination with the `with` statement to execute code while the object is bound to the thread. @@ -126,6 +142,7 @@ self._thread_context = thread_local() self._greenlet_context_lock = GreenletRLock() self._greenlet_context = greenlet_local() + self._context_stack = ContextVar('stack') self._cache = {} self._stackop = get_iterator_next_method(count()) @@ -134,15 +151,28 @@ application and context cache. """ use_gevent = is_gevent_enabled() - tid = greenlet_get_ident() if use_gevent else thread_get_ident() + use_context = is_context_enabled() + + if use_gevent: + tid = greenlet_get_ident() + elif use_context: + tid = context_get_ident() + else: + tid = thread_get_ident() + objects = self._cache.get(tid) if objects is None: if len(self._cache) > _MAX_CONTEXT_OBJECT_CACHE: self._cache.clear() objects = self._global[:] objects.extend(getattr(self._thread_context, 'stack', ())) + if use_gevent: objects.extend(getattr(self._greenlet_context, 'stack', ())) + + if use_context: + objects.extend(self._context_stack.get([])) + objects.sort(reverse=True) objects = [x[1] for x in objects] self._cache[tid] = objects @@ -173,6 +203,22 @@ finally: self._greenlet_context_lock.release() + def push_context(self, obj): + self._cache.pop(context_get_ident(), None) + item = (self._stackop(), obj) + stack = self._context_stack.get(None) + if stack is None: + stack = [item] + self._context_stack.set(stack) + else: + stack.append(item) + + def pop_context(self): + self._cache.pop(context_get_ident(), None) + stack = self._context_stack.get(None) + assert stack, 'no objects on stack' + return stack.pop()[1] + def push_thread(self, obj): self._thread_context_lock.acquire() try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/_speedups.pyx new/Logbook-1.5.2/logbook/_speedups.pyx --- old/Logbook-1.4.3/logbook/_speedups.pyx 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/_speedups.pyx 2019-08-21 06:33:30.000000000 +0200 @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# cython: language_level=2 """ logbook._speedups ~~~~~~~~~~~~~~~~~ @@ -9,17 +10,17 @@ :license: BSD, see LICENSE for more details. """ -import platform + from logbook.concurrency import (is_gevent_enabled, thread_get_ident, greenlet_get_ident, thread_local, - GreenletRLock, greenlet_local) + GreenletRLock, greenlet_local, ContextVar, context_get_ident, is_context_enabled) from cpython.dict cimport PyDict_Clear, PyDict_SetItem -from cpython.list cimport PyList_New, PyList_Append, PyList_Sort, \ - PyList_SET_ITEM, PyList_GET_SIZE +from cpython.list cimport PyList_Append, PyList_Sort, PyList_GET_SIZE + from cpython.pythread cimport PyThread_type_lock, PyThread_allocate_lock, \ PyThread_release_lock, PyThread_acquire_lock, WAIT_LOCK -cdef object _missing = object() +_missing = object() cdef enum: _MAX_CONTEXT_OBJECT_CACHE = 256 @@ -40,7 +41,7 @@ def __get__(self, obj, type): if obj is None: return self - rv = getattr3(obj, self._name, _missing) + rv = getattr(obj, self._name, _missing) if rv is not _missing and rv != self.fallback: return rv if obj.group is None: @@ -96,9 +97,16 @@ cdef class StackedObject: - """Baseclass for all objects that provide stack manipulation + """Base class for all objects that provide stack manipulation operations. """ + cpdef push_context(self): + """Pushes the stacked object to the asyncio (via contextvar) stack.""" + raise NotImplementedError() + + cpdef pop_context(self): + """Pops the stacked object from the asyncio (via contextvar) stack.""" + raise NotImplementedError() cpdef push_greenlet(self): """Pushes the stacked object to the greenlet stack.""" @@ -155,6 +163,12 @@ """ return _StackBound(self, self.push_application, self.pop_application) + cpdef contextbound(self): + """Can be used in combination with the `with` statement to + execute code while the object is bound to the asyncio context. + """ + return _StackBound(self, self.push_context, self.pop_context) + cdef class ContextStackManager: cdef list _global @@ -162,6 +176,7 @@ cdef object _thread_context cdef object _greenlet_context_lock cdef object _greenlet_context + cdef object _context_stack cdef dict _cache cdef int _stackcnt @@ -171,6 +186,7 @@ self._thread_context = thread_local() self._greenlet_context_lock = GreenletRLock() self._greenlet_context = greenlet_local() + self._context_stack = ContextVar('stack') self._cache = {} self._stackcnt = 0 @@ -180,15 +196,28 @@ cpdef iter_context_objects(self): use_gevent = is_gevent_enabled() - tid = greenlet_get_ident() if use_gevent else thread_get_ident() + use_context = is_context_enabled() + + if use_gevent: + tid = greenlet_get_ident() + elif use_context: + tid = context_get_ident() + else: + tid = thread_get_ident() + objects = self._cache.get(tid) if objects is None: if PyList_GET_SIZE(self._cache) > _MAX_CONTEXT_OBJECT_CACHE: PyDict_Clear(self._cache) objects = self._global[:] - objects.extend(getattr3(self._thread_context, 'stack', ())) + objects.extend(getattr(self._thread_context, 'stack', ())) + if use_gevent: - objects.extend(getattr3(self._greenlet_context, 'stack', ())) + objects.extend(getattr(self._greenlet_context, 'stack', ())) + + if use_context: + objects.extend(self._context_stack.get([])) + PyList_Sort(objects) objects = [(<_StackItem>x).val for x in objects] PyDict_SetItem(self._cache, tid, objects) @@ -199,7 +228,7 @@ try: self._cache.pop(greenlet_get_ident(), None) item = _StackItem(self._stackop(), obj) - stack = getattr3(self._greenlet_context, 'stack', None) + stack = getattr(self._greenlet_context, 'stack', None) if stack is None: self._greenlet_context.stack = [item] else: @@ -211,18 +240,35 @@ self._greenlet_context_lock.acquire() try: self._cache.pop(greenlet_get_ident(), None) - stack = getattr3(self._greenlet_context, 'stack', None) + stack = getattr(self._greenlet_context, 'stack', None) assert stack, 'no objects on stack' return (<_StackItem>stack.pop()).val finally: self._greenlet_context_lock.release() + cpdef push_context(self, obj): + self._cache.pop(context_get_ident(), None) + item = _StackItem(self._stackop(), obj) + stack = self._context_stack.get(None) + + if stack is None: + stack = [item] + self._context_stack.set(stack) + else: + PyList_Append(stack, item) + + cpdef pop_context(self): + self._cache.pop(context_get_ident(), None) + stack = self._context_stack.get(None) + assert stack, 'no objects on stack' + return (<_StackItem>stack.pop()).val + cpdef push_thread(self, obj): PyThread_acquire_lock(self._thread_context_lock, WAIT_LOCK) try: self._cache.pop(thread_get_ident(), None) item = _StackItem(self._stackop(), obj) - stack = getattr3(self._thread_context, 'stack', None) + stack = getattr(self._thread_context, 'stack', None) if stack is None: self._thread_context.stack = [item] else: @@ -234,7 +280,7 @@ PyThread_acquire_lock(self._thread_context_lock, WAIT_LOCK) try: self._cache.pop(thread_get_ident(), None) - stack = getattr3(self._thread_context, 'stack', None) + stack = getattr(self._thread_context, 'stack', None) assert stack, 'no objects on stack' return (<_StackItem>stack.pop()).val finally: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/base.py new/Logbook-1.5.2/logbook/base.py --- old/Logbook-1.4.3/logbook/base.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/base.py 2019-08-21 06:33:30.000000000 +0200 @@ -23,9 +23,15 @@ parse_iso8601, string_types, to_safe_json, u, xrange) +_has_speedups = False try: + if os.environ.get('DISABLE_LOGBOOK_CEXT_AT_RUNTIME'): + raise ImportError("Speedups disabled via DISABLE_LOGBOOK_CEXT_AT_RUNTIME") + from logbook._speedups import ( _missing, group_reflected_property, ContextStackManager, StackedObject) + + _has_speedups = True except ImportError: from logbook._fallback import ( _missing, group_reflected_property, ContextStackManager, StackedObject) @@ -73,7 +79,7 @@ logbook.set_datetime_format("local") Other uses rely on your supplied :py:obj:`datetime_format`. - Using `pytz <https://pypi.python.org/pypi/pytz>`_ for example:: + Using `pytz <https://pypi.org/pypi/pytz>`_ for example:: from datetime import datetime import logbook @@ -206,6 +212,15 @@ popped = self.stack_manager.pop_greenlet() assert popped is self, 'popped unexpected object' + def push_context(self): + """Pushes the context object to the context stack.""" + self.stack_manager.push_context(self) + + def pop_context(self): + """Pops the context object from the stack.""" + popped = self.stack_manager.pop_context() + assert popped is self, 'popped unexpected object' + def push_thread(self): """Pushes the context object to the thread stack.""" self.stack_manager.push_thread(self) @@ -257,6 +272,14 @@ for obj in reversed(self.objects): obj.pop_greenlet() + def push_context(self): + for obj in self.objects: + obj.push_context() + + def pop_context(self): + for obj in reversed(self.objects): + obj.pop_context() + class Processor(ContextObject): """Can be pushed to a stack to inject additional information into diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/concurrency.py new/Logbook-1.5.2/logbook/concurrency.py --- old/Logbook-1.4.3/logbook/concurrency.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/concurrency.py 2019-08-21 06:33:30.000000000 +0200 @@ -164,3 +164,53 @@ return GreenletRLock() else: return ThreadRLock() + + +has_contextvars = True +try: + import contextvars +except ImportError: + has_contextvars = False + +if has_contextvars: + from contextvars import ContextVar + from itertools import count + + context_ident_counter = count() + context_ident = ContextVar('context_ident') + + def context_get_ident(): + try: + return context_ident.get() + except LookupError: + ident = 'context-%s' % next(context_ident_counter) + context_ident.set(ident) + return ident + + def is_context_enabled(): + try: + context_ident.get() + return True + except LookupError: + return False + +else: + class ContextVar(object): + def __init__(self, name): + self.name = name + self.local = thread_local() + + def set(self, value): + self.local = value + + def get(self, default=None): + if self.local is None: + return default + + return default + + def context_get_ident(): + return 1 + + def is_context_enabled(): + return False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/handlers.py new/Logbook-1.5.2/logbook/handlers.py --- old/Logbook-1.4.3/logbook/handlers.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/handlers.py 2019-08-21 06:33:30.000000000 +0200 @@ -1900,6 +1900,10 @@ Handler.pop_thread(self) self.rollover() + def pop_context(self): + Handler.pop_context(self) + self.rollover() + def pop_greenlet(self): Handler.pop_greenlet(self) self.rollover() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/more.py new/Logbook-1.5.2/logbook/more.py --- old/Logbook-1.4.3/logbook/more.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/more.py 2019-08-21 06:33:30.000000000 +0200 @@ -327,7 +327,7 @@ .. versionchanged:: 1.0.0 Added Windows support if `colorama`_ is installed. - .. _`colorama`: https://pypi.python.org/pypi/colorama + .. _`colorama`: https://pypi.org/pypi/colorama """ _use_color = None @@ -383,7 +383,7 @@ .. versionchanged:: 1.0 Added Windows support if `colorama`_ is installed. - .. _`colorama`: https://pypi.python.org/pypi/colorama + .. _`colorama`: https://pypi.org/pypi/colorama """ def __init__(self, *args, **kwargs): StderrHandler.__init__(self, *args, **kwargs) @@ -473,6 +473,10 @@ Handler.pop_thread(self) self.flush() + def pop_context(self): + Handler.pop_context(self) + self.flush() + def pop_greenlet(self): Handler.pop_greenlet(self) self.flush() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/logbook/queues.py new/Logbook-1.5.2/logbook/queues.py --- old/Logbook-1.4.3/logbook/queues.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/logbook/queues.py 2019-08-21 06:33:30.000000000 +0200 @@ -604,7 +604,10 @@ queue and sends it to a handler. Both queue and handler are taken from the passed :class:`ThreadedWrapperHandler`. """ - _sentinel = object() + class Command(object): + stop = object() + emit = object() + emit_batch = object() def __init__(self, wrapper_handler): self.wrapper_handler = wrapper_handler @@ -621,17 +624,23 @@ def stop(self): """Stops the task thread.""" if self.running: - self.wrapper_handler.queue.put_nowait(self._sentinel) + self.wrapper_handler.queue.put_nowait((self.Command.stop, )) self._thread.join() self._thread = None def _target(self): while 1: - record = self.wrapper_handler.queue.get() - if record is self._sentinel: + item = self.wrapper_handler.queue.get() + command, data = item[0], item[1:] + if command is self.Command.stop: self.running = False break - self.wrapper_handler.handler.handle(record) + elif command is self.Command.emit: + (record, ) = data + self.wrapper_handler.handler.emit(record) + elif command is self.Command.emit_batch: + record, reason = data + self.wrapper_handler.handler.emit_batch(record, reason) class ThreadedWrapperHandler(WrapperHandler): @@ -663,8 +672,17 @@ self.handler.close() def emit(self, record): + item = (TWHThreadController.Command.emit, record) try: - self.queue.put_nowait(record) + self.queue.put_nowait(item) + except Full: + # silently drop + pass + + def emit_batch(self, records, reason): + item = (TWHThreadController.Command.emit_batch, records, reason) + try: + self.queue.put_nowait(item) except Full: # silently drop pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/scripts/test_setup.py new/Logbook-1.5.2/scripts/test_setup.py --- old/Logbook-1.4.3/scripts/test_setup.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/scripts/test_setup.py 2019-08-21 06:33:30.000000000 +0200 @@ -1,5 +1,5 @@ #! /usr/bin/python -import pip +from pip._internal import main as pip_main import sys if __name__ == '__main__': @@ -14,5 +14,5 @@ ] print("Setting up dependencies...") - result = pip.main(["install"] + deps) + result = pip_main(["install"] + deps) sys.exit(result) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/tests/conftest.py new/Logbook-1.5.2/tests/conftest.py --- old/Logbook-1.4.3/tests/conftest.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/tests/conftest.py 2019-08-21 06:33:30.000000000 +0200 @@ -1,3 +1,5 @@ +import sys + import logbook import pytest @@ -106,3 +108,10 @@ @request.addfinalizer def fin(): _disable_gevent() + + +def pytest_ignore_collect(path, config): + if 'test_asyncio.py' in path.basename and (sys.version_info.major < 3 or sys.version_info.minor < 5): + return True + + return False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/tests/test_asyncio.py new/Logbook-1.5.2/tests/test_asyncio.py --- old/Logbook-1.4.3/tests/test_asyncio.py 1970-01-01 01:00:00.000000000 +0100 +++ new/Logbook-1.5.2/tests/test_asyncio.py 2019-08-21 06:33:30.000000000 +0200 @@ -0,0 +1,27 @@ +import pytest +import logbook +import asyncio +from logbook.concurrency import has_contextvars + +ITERATIONS = 100 + + +@pytest.mark.skipif(not has_contextvars, reason="Contexvars not available") +def test_asyncio_context_management(logger): + h1 = logbook.TestHandler() + h2 = logbook.TestHandler() + + async def task(handler, msg): + for _ in range(ITERATIONS): + with handler.contextbound(): + logger.info(msg) + + await asyncio.sleep(0) # allow for context switch + + asyncio.get_event_loop().run_until_complete(asyncio.gather(task(h1, 'task1'), task(h2, 'task2'))) + + assert len(h1.records) == ITERATIONS + assert all(['task1' == r.msg for r in h1.records]) + + assert len(h2.records) == ITERATIONS + assert all(['task2' == r.msg for r in h2.records]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/tests/test_queues.py new/Logbook-1.5.2/tests/test_queues.py --- old/Logbook-1.4.3/tests/test_queues.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/tests/test_queues.py 2019-08-21 06:33:30.000000000 +0200 @@ -89,9 +89,24 @@ assert test_handler.has_warning('Hello World') +class BatchTestHandler(logbook.TestHandler): + def __init__(self, *args, **kwargs): + super(BatchTestHandler, self).__init__(*args, **kwargs) + self.batches = [] + + def emit(self, record): + super(BatchTestHandler, self).emit(record) + self.batches.append([record]) + + def emit_batch(self, records, reason): + for record in records: + super(BatchTestHandler, self).emit(record) + self.batches.append(records) + + def test_threaded_wrapper_handler(logger): from logbook.queues import ThreadedWrapperHandler - test_handler = logbook.TestHandler() + test_handler = BatchTestHandler() with ThreadedWrapperHandler(test_handler) as handler: logger.warn('Just testing') logger.error('More testing') @@ -100,6 +115,50 @@ handler.close() assert (not handler.controller.running) + assert len(test_handler.records) == 2 + assert len(test_handler.batches) == 2 + assert all((len(records) == 1 for records in test_handler.batches)) + assert test_handler.has_warning('Just testing') + assert test_handler.has_error('More testing') + + +def test_threaded_wrapper_handler_emit(): + from logbook.queues import ThreadedWrapperHandler + test_handler = BatchTestHandler() + with ThreadedWrapperHandler(test_handler) as handler: + lr = logbook.LogRecord('Test Logger', logbook.WARNING, 'Just testing') + test_handler.emit(lr) + lr = logbook.LogRecord('Test Logger', logbook.ERROR, 'More testing') + test_handler.emit(lr) + + # give it some time to sync up + handler.close() + + assert (not handler.controller.running) + assert len(test_handler.records) == 2 + assert len(test_handler.batches) == 2 + assert all((len(records) == 1 for records in test_handler.batches)) + assert test_handler.has_warning('Just testing') + assert test_handler.has_error('More testing') + + +def test_threaded_wrapper_handler_emit_batched(): + from logbook.queues import ThreadedWrapperHandler + test_handler = BatchTestHandler() + with ThreadedWrapperHandler(test_handler) as handler: + test_handler.emit_batch([ + logbook.LogRecord('Test Logger', logbook.WARNING, 'Just testing'), + logbook.LogRecord('Test Logger', logbook.ERROR, 'More testing'), + ], 'group') + + # give it some time to sync up + handler.close() + + assert (not handler.controller.running) + assert len(test_handler.records) == 2 + assert len(test_handler.batches) == 1 + (records, ) = test_handler.batches + assert len(records) == 2 assert test_handler.has_warning('Just testing') assert test_handler.has_error('More testing') @@ -164,10 +223,10 @@ import redis from logbook.queues import RedisHandler - KEY = 'redis' + KEY = 'redis-{}'.format(os.getpid()) FIELDS = ['message', 'host'] r = redis.Redis(decode_responses=True) - redis_handler = RedisHandler(level=logbook.INFO, bubble=True) + redis_handler = RedisHandler(key=KEY, level=logbook.INFO, bubble=True) # We don't want output for the tests, so we can wrap everything in a # NullHandler null_handler = logbook.NullHandler() @@ -185,7 +244,7 @@ assert message.find(LETTERS) # Change the key of the handler and check on redis - KEY = 'test_another_key' + KEY = 'test_another_key-{}'.format(os.getpid()) redis_handler.key = KEY with null_handler.applicationbound(): @@ -234,7 +293,8 @@ from logbook.queues import RedisHandler null_handler = logbook.NullHandler() - redis_handler = RedisHandler(key='lpushed', push_method='lpush', + KEY = 'lpushed-'.format(os.getpid()) + redis_handler = RedisHandler(key=KEY, push_method='lpush', level=logbook.INFO, bubble=True) with null_handler.applicationbound(): @@ -245,10 +305,10 @@ time.sleep(1.5) r = redis.Redis(decode_responses=True) - logs = r.lrange('lpushed', 0, -1) + logs = r.lrange(KEY, 0, -1) assert logs assert "new item" in logs[0] - r.delete('lpushed') + r.delete(KEY) @require_module('redis') @@ -261,7 +321,8 @@ from logbook.queues import RedisHandler null_handler = logbook.NullHandler() - redis_handler = RedisHandler(key='rpushed', push_method='rpush', + KEY = 'rpushed-' + str(os.getpid()) + redis_handler = RedisHandler(key=KEY, push_method='rpush', level=logbook.INFO, bubble=True) with null_handler.applicationbound(): @@ -272,10 +333,10 @@ time.sleep(1.5) r = redis.Redis(decode_responses=True) - logs = r.lrange('rpushed', 0, -1) + logs = r.lrange(KEY, 0, -1) assert logs assert "old item" in logs[0] - r.delete('rpushed') + r.delete(KEY) @pytest.fixture diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/tests/test_syslog_handler.py new/Logbook-1.5.2/tests/test_syslog_handler.py --- old/Logbook-1.4.3/tests/test_syslog_handler.py 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/tests/test_syslog_handler.py 2019-08-21 06:33:30.000000000 +0200 @@ -4,66 +4,68 @@ from contextlib import closing import logbook -from logbook.helpers import u - import pytest -unix_socket = "/tmp/__unixsock_logbook.test" +UNIX_SOCKET = "/tmp/__unixsock_logbook.test" + +DELIMITERS = { + socket.AF_INET: '\n' +} -to_test = [ +TO_TEST = [ (socket.AF_INET, socket.SOCK_DGRAM, ('127.0.0.1', 0)), (socket.AF_INET, socket.SOCK_STREAM, ('127.0.0.1', 0)), ] -if hasattr(socket, 'AF_UNIX'): - to_test.append((socket.AF_UNIX, socket.SOCK_DGRAM, unix_socket)) + +UNIX_SOCKET_AVAILABLE = hasattr(socket, 'AF_UNIX') + +if UNIX_SOCKET_AVAILABLE: + DELIMITERS[socket.AF_UNIX] = '\x00' + TO_TEST.append((socket.AF_UNIX, socket.SOCK_DGRAM, UNIX_SOCKET)) + @pytest.mark.usefixtures("unix_sock_path") -@pytest.mark.parametrize("sock_family,socktype,address", to_test) -def test_syslog_handler(logger, activation_strategy, - sock_family, socktype, address): - delimiter = {socket.AF_UNIX: '\x00', - socket.AF_INET: '\n'}[sock_family] +@pytest.mark.parametrize("sock_family,socktype,address", TO_TEST) +@pytest.mark.parametrize("app_name", [None, 'Testing']) +def test_syslog_handler(logger, activation_strategy, sock_family, socktype, address, app_name): + delimiter = DELIMITERS[sock_family] with closing(socket.socket(sock_family, socktype)) as inc: inc.bind(address) + if socktype == socket.SOCK_STREAM: inc.listen(0) + inc.settimeout(1) - for app_name in [None, 'Testing']: - if sock_family == socket.AF_UNIX: - expected = (r'^<12>%stestlogger: Syslog is weird%s$' % - (app_name + ':' if app_name else '', - delimiter)) - else: - expected = (r'^<12>1 \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z %s %s %d ' - '- - %sSyslog is weird%s$' % - (socket.gethostname(), - app_name if app_name else 'testlogger', - os.getpid(), 'testlogger: ' if app_name else '', - delimiter)) - - handler = logbook.SyslogHandler(app_name, inc.getsockname(), - socktype=socktype) - with activation_strategy(handler): - logger.warn('Syslog is weird') - try: - if socktype == socket.SOCK_STREAM: - with closing(inc.accept()[0]) as inc2: - rv = inc2.recv(1024) - else: - rv = inc.recvfrom(1024)[0] - except socket.error: - assert False, 'got timeout on socket' - rv = rv.decode('utf-8') - assert re.match(expected, rv), \ - 'expected {}, got {}'.format(expected, rv) + if UNIX_SOCKET_AVAILABLE and sock_family == socket.AF_UNIX: + expected = (r'^<12>%stestlogger: Syslog is weird%s$' % (app_name + ':' if app_name else '', delimiter)) + else: + expected = (r'^<12>1 \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z %s %s %d - - %sSyslog is weird%s$' % ( + socket.gethostname(), + app_name if app_name else 'testlogger', + os.getpid(), 'testlogger: ' if app_name else '', + delimiter)) + + handler = logbook.SyslogHandler(app_name, inc.getsockname(), socktype=socktype) + + with activation_strategy(handler): + logger.warn('Syslog is weird') + + if socktype == socket.SOCK_STREAM: + with closing(inc.accept()[0]) as inc2: + rv = inc2.recv(1024) + else: + rv = inc.recvfrom(1024)[0] + + rv = rv.decode('utf-8') + assert re.match(expected, rv), \ + 'expected {}, got {}'.format(expected, rv) -@pytest.fixture -def unix_sock_path(request): - returned = unix_socket - @request.addfinalizer - def cleanup(): - if os.path.exists(returned): - os.unlink(returned) - return returned +@pytest.fixture +def unix_sock_path(): + try: + yield UNIX_SOCKET + finally: + if os.path.exists(UNIX_SOCKET): + os.unlink(UNIX_SOCKET) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Logbook-1.4.3/tox.ini new/Logbook-1.5.2/tox.ini --- old/Logbook-1.4.3/tox.ini 2019-01-16 21:34:24.000000000 +0100 +++ new/Logbook-1.5.2/tox.ini 2019-08-21 06:33:30.000000000 +0200 @@ -1,35 +1,32 @@ [tox] -envlist=py27,py34,py35,py36,pypy,docs -skipsdist=True +envlist = py{27,35,36,37}{,-speedups},pypy,py37-docs +skipsdist = True [testenv] -whitelist_externals= - rm -deps= - py{27}: mock - pytest - Cython -changedir={toxinidir} -commands= - {envbindir}/cython logbook/_speedups.pyx - {envpython} {toxinidir}/setup.py develop - {envpython} {toxinidir}/scripts/test_setup.py - py.test {toxinidir}/tests - rm -f {toxinidir}/logbook/_speedups.\{so,c\} +whitelist_externals = + rm +deps = + py{27}: mock + pytest + speedups: Cython +setenv = + !speedups: DISABLE_LOGBOOK_CEXT=1 + !speedups: DISABLE_LOGBOOK_CEXT_AT_RUNTIME=1 +changedir = {toxinidir} +commands = + {envpython} -m pip install -e {toxinidir}[all] -[testenv:pypy] -deps= - mock - pytest -commands= - {envpython} {toxinidir}/setup.py develop - {envpython} {toxinidir}/scripts/test_setup.py - py.test {toxinidir}/tests + # Make sure that speedups are available/not available, as needed. + speedups: {envpython} -c "from logbook.base import _has_speedups; exit(0 if _has_speedups else 1)" + !speedups: {envpython} -c "from logbook.base import _has_speedups; exit(1 if _has_speedups else 0)" -[testenv:docs] -deps= - Sphinx==1.1.3 -changedir=docs -commands= - sphinx-build -W -b html . _build/html - sphinx-build -W -b linkcheck . _build/linkcheck + {envpython} {toxinidir}/scripts/test_setup.py + py.test {toxinidir}/tests + +[testenv:py37-docs] +deps = + Sphinx>=1.3 +changedir = docs +commands = + sphinx-build -W -b html . _build/html + sphinx-build -W -b linkcheck . _build/linkcheck
participants (1)
-
root