Repository: hynek/structlog

Issue:
Consistently name threadlocal and contextvars helper functions
The threadlocal and contextvars integrations use a different naming for their functions to bind, unbind, merge and clear the context. This came up as part of #236 and e3a6213afb2844572c8890693a83e142c41b1f0b.

I'd like to propose the following function names:

- `contextvars`
  - `merge_contextvars_context`
  - `clear_contextvars_context`
  - `bind_to_contextvars_context`
  - `unbind_from_contextvars_context`
- `threadlocal`
  - `merge_threadlocal_context`
  - `clear_threadlocal_context`
  - `bind_to_threadlocal_context`
  - `unbind_from_threadlocal_context` -- doesn't exist but should probably be added

Comments:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fe05562..3ab315f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -25,7 +25,7 @@ Deprecations:
 Changes:
 ^^^^^^^^
 
-- Added a new module ``structlog.contextvars`` that allows to have a global but context-local ``structlog`` context the same way as with ``structlog.threalocal`` since 19.2.0.
+- Added a new module ``structlog.contextvars`` that allows to have a global but context-local ``structlog`` context the same way as with ``structlog.threadlocal`` since 19.2.0.
   `#201 <https://github.com/hynek/structlog/issues/201>`_
   `#236 <https://github.com/hynek/structlog/pull/236>`_
 - Added a new module ``structlog.testing`` for first class testing support.
diff --git a/docs/api.rst b/docs/api.rst
index bf35681..f1f5220 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -85,7 +85,7 @@ API Reference
 
 .. automodule:: structlog.threadlocal
 
-.. autofunction:: merge_threadlocal_context
+.. autofunction:: merge_threadlocal
 
 .. autofunction:: clear_threadlocal
 
@@ -114,7 +114,7 @@ API Reference
 
 .. automodule:: structlog.contextvars
 
-.. autofunction:: merge_contextvars_context
+.. autofunction:: merge_contextvars
 .. autofunction:: clear_contextvars
 .. autofunction:: bind_contextvars
 .. autofunction:: unbind_contextvars
diff --git a/docs/contextvars.rst b/docs/contextvars.rst
index a23edb6..6add8a5 100644
--- a/docs/contextvars.rst
+++ b/docs/contextvars.rst
@@ -19,31 +19,31 @@ For that ``structlog`` provides a set of functions to bind variables to a contex
 This context is safe to be used in asynchronous code.
 The functions are:
 
-- :func:`structlog.contextvars.merge_contextvars_context`,
+- :func:`structlog.contextvars.merge_contextvars`,
 - :func:`structlog.contextvars.clear_contextvars`,
 - :func:`structlog.contextvars.bind_contextvars`,
 - :func:`structlog.contextvars.unbind_contextvars`,
 
 The general flow of using these functions is:
 
-- Use :func:`structlog.configure` with :func:`structlog.contextvars.merge_contextvars_context` as your first processor.
+- Use :func:`structlog.configure` with :func:`structlog.contextvars.merge_contextvars` as your first processor.
 - Call :func:`structlog.contextvars.clear_contextvars` at the beginning of your request handler (or whenever you want to reset the context-local context).
 - Call :func:`structlog.contextvars.bind_contextvars` and :func:`structlog.contextvars.unbind_contextvars` instead of :func:`structlog.BoundLogger.bind` and :func:`structlog.BoundLogger.unbind` when you want to (un)bind a particular variable to the context-local context.
 - Use ``structlog`` as normal.
-  Loggers act as the always do, but the :func:`structlog.contextvars.merge_contextvars_context` processor ensures that any context-local binds get included in all of your log messages.
+  Loggers act as the always do, but the :func:`structlog.contextvars.merge_contextvars` processor ensures that any context-local binds get included in all of your log messages.
 
 .. doctest::
 
    >>> from structlog.contextvars import (
    ...     bind_contextvars,
    ...     clear_contextvars,
-   ...     merge_contextvars_context,
+   ...     merge_contextvars,
    ...     unbind_contextvars,
    ... )
    >>> from structlog import configure
    >>> configure(
    ...     processors=[
-   ...         merge_contextvars_context,
+   ...         merge_contextvars,
    ...         structlog.processors.KeyValueRenderer(),
    ...     ]
    ... )
diff --git a/docs/thread-local.rst b/docs/thread-local.rst
index c0282d4..fd9f581 100644
--- a/docs/thread-local.rst
+++ b/docs/thread-local.rst
@@ -33,31 +33,31 @@ However, in the case of conventional web development, we realize that passing lo
 And since it's more important that people actually *use* ``structlog`` than to be pure and snobby, ``structlog`` contains a couple of mechanisms to help here.
 
 
-The ``merge_threadlocal_context`` Processor
--------------------------------------------
+The ``merge_threadlocal`` Processor
+-----------------------------------
 
 ``structlog`` provides a simple set of functions that allow explicitly binding certain fields to a global (thread-local) context.
-These functions are :func:`structlog.threadlocal.merge_threadlocal_context`, :func:`structlog.threadlocal.clear_threadlocal`, and :func:`structlog.threadlocal.bind_threadlocal`.
+These functions are :func:`structlog.threadlocal.merge_threadlocal`, :func:`structlog.threadlocal.clear_threadlocal`, and :func:`structlog.threadlocal.bind_threadlocal`.
 
 The general flow of using these functions is:
 
-- Use :func:`structlog.configure` with :func:`structlog.threadlocal.merge_threadlocal_context` as your first processor.
+- Use :func:`structlog.configure` with :func:`structlog.threadlocal.merge_threadlocal` as your first processor.
 - Call :func:`structlog.threadlocal.clear_threadlocal` at the beginning of your request handler (or whenever you want to reset the thread-local context).
 - Call :func:`structlog.threadlocal.bind_threadlocal` as an alternative to :func:`structlog.BoundLogger.bind` when you want to bind a particular variable to the thread-local context.
 - Use ``structlog`` as normal.
-  Loggers act as the always do, but the :func:`structlog.threadlocal.merge_threadlocal_context` processor ensures that any thread-local binds get included in all of your log messages.
+  Loggers act as the always do, but the :func:`structlog.threadlocal.merge_threadlocal` processor ensures that any thread-local binds get included in all of your log messages.
 
 .. doctest::
 
    >>> from structlog.threadlocal import (
    ...     bind_threadlocal,
    ...     clear_threadlocal,
-   ...     merge_threadlocal_context,
+   ...     merge_threadlocal,
    ... )
    >>> from structlog import configure
    >>> configure(
    ...     processors=[
-   ...         merge_threadlocal_context,
+   ...         merge_threadlocal,
    ...         structlog.processors.KeyValueRenderer(),
    ...     ]
    ... )
diff --git a/src/structlog/_base.py b/src/structlog/_base.py
index 3b5c461..23acbc1 100644
--- a/src/structlog/_base.py
+++ b/src/structlog/_base.py
@@ -86,7 +86,7 @@ class BoundLoggerBase(object):
 
     def try_unbind(self, *keys):
         """
-        Like :meth:`unbind`, but best effort:  missing keys are ignored.
+        Like :meth:`unbind`, but best effort: missing keys are ignored.
 
         :rtype: `self.__class__`
 
@@ -94,10 +94,8 @@ class BoundLoggerBase(object):
         """
         bl = self.bind()
         for key in keys:
-            try:
-                del bl._context[key]
-            except KeyError:
-                pass
+            bl._context.pop(key, None)
+
         return bl
 
     def new(self, **new_values):
diff --git a/src/structlog/contextvars.py b/src/structlog/contextvars.py
index c780f5b..4fc0bd6 100644
--- a/src/structlog/contextvars.py
+++ b/src/structlog/contextvars.py
@@ -19,7 +19,7 @@ import contextvars
 _CONTEXT = contextvars.ContextVar("structlog_context")
 
 
-def merge_contextvars_context(logger, method_name, event_dict):
+def merge_contextvars(logger, method_name, event_dict):
     """
     A processor that merges in a global (context-local) context.
 
@@ -58,9 +58,9 @@ def bind_contextvars(**kwargs):
     _get_context().update(kwargs)
 
 
-def unbind_contextvars(*args):
+def unbind_contextvars(*keys):
     """
-    Remove keys from the context-local context.
+    Remove *keys* from the context-local context if they are present.
 
     Use this instead of :func:`~structlog.BoundLogger.unbind` when you want to
     remove keys from a global (context-local) context.
@@ -68,7 +68,7 @@ def unbind_contextvars(*args):
     .. versionadded:: 20.1.0
     """
     ctx = _get_context()
-    for key in args:
+    for key in keys:
         ctx.pop(key, None)
 
 
diff --git a/src/structlog/threadlocal.py b/src/structlog/threadlocal.py
index ac7d0ad..c5e4a58 100644
--- a/src/structlog/threadlocal.py
+++ b/src/structlog/threadlocal.py
@@ -170,24 +170,36 @@ class _ThreadLocalDictWrapper(object):
 _CONTEXT = threading.local()
 
 
-def merge_threadlocal_context(logger, method_name, event_dict):
+def merge_threadlocal(logger, method_name, event_dict):
     """
     A processor that merges in a global (thread-local) context.
 
     Use this as your first processor in :func:`structlog.configure` to ensure
     thread-local context is included in all log calls.
+
+    .. versionadded:: 19.2.0
+
+    .. versionchanged:: 20.1.0
+       This function used to be called ``merge_threalocal_context`` and that
+       name is still kept around for backward compatability.
     """
     context = _get_context().copy()
     context.update(event_dict)
     return context
 
 
+# Alias that shouldn't be used anymore.
+merge_threadlocal_context = merge_threadlocal
+
+
 def clear_threadlocal():
     """
     Clear the thread-local context.
 
     The typical use-case for this function is to invoke it early in
     request-handling code.
+
+    .. versionadded:: 19.2.0
     """
     _CONTEXT.context = {}
 
@@ -198,6 +210,8 @@ def bind_threadlocal(**kwargs):
 
     Use this instead of :func:`~structlog.BoundLogger.bind` when you want some
     context to be global (thread-local).
+
+    .. versionadded:: 19.2.0
     """
     _get_context().update(kwargs)
 


Test patch:
diff --git a/tests/test_contextvars.py b/tests/test_contextvars.py
index 1257230..c9cf1b0 100644
--- a/tests/test_contextvars.py
+++ b/tests/test_contextvars.py
@@ -7,7 +7,7 @@ import pytest
 from structlog.contextvars import (
     bind_contextvars,
     clear_contextvars,
-    merge_contextvars_context,
+    merge_contextvars,
     unbind_contextvars,
 )
 
@@ -20,12 +20,12 @@ class TestNewContextvars(object):
     async def test_bind(self, event_loop):
         """
         Binding a variable causes it to be included in the result of
-        merge_contextvars_context.
+        merge_contextvars.
         """
 
         async def coro():
             bind_contextvars(a=1)
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         assert {"a": 1, "b": 2} == await event_loop.create_task(coro())
 
@@ -38,7 +38,7 @@ class TestNewContextvars(object):
         async def coro():
             bind_contextvars(a=1, c=3)
             bind_contextvars(c=333, d=4)
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         assert {
             "a": 1,
@@ -55,7 +55,7 @@ class TestNewContextvars(object):
         async def coro():
             bind_contextvars(a=1)
             await event_loop.create_task(nested_coro())
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         async def nested_coro():
             bind_contextvars(c=3)
@@ -64,62 +64,62 @@ class TestNewContextvars(object):
 
     async def test_merge_works_without_bind(self, event_loop):
         """
-        merge_contextvars_context returns values as normal even when there has
+        merge_contextvars returns values as normal even when there has
         been no previous calls to bind_contextvars.
         """
 
         async def coro():
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         assert {"b": 2} == await event_loop.create_task(coro())
 
     async def test_merge_overrides_bind(self, event_loop):
         """
-        Variables included in merge_contextvars_context override previously
+        Variables included in merge_contextvars override previously
         bound variables.
         """
 
         async def coro():
             bind_contextvars(a=1)
-            return merge_contextvars_context(None, None, {"a": 111, "b": 2})
+            return merge_contextvars(None, None, {"a": 111, "b": 2})
 
         assert {"a": 111, "b": 2} == await event_loop.create_task(coro())
 
     async def test_clear(self, event_loop):
         """
         The context-local context can be cleared, causing any previously bound
-        variables to not be included in merge_contextvars_context's result.
+        variables to not be included in merge_contextvars's result.
         """
 
         async def coro():
             bind_contextvars(a=1)
             clear_contextvars()
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         assert {"b": 2} == await event_loop.create_task(coro())
 
     async def test_clear_without_bind(self, event_loop):
         """
         The context-local context can be cleared, causing any previously bound
-        variables to not be included in merge_contextvars_context's result.
+        variables to not be included in merge_contextvars's result.
         """
 
         async def coro():
             clear_contextvars()
-            return merge_contextvars_context(None, None, {})
+            return merge_contextvars(None, None, {})
 
         assert {} == await event_loop.create_task(coro())
 
     async def test_undbind(self, event_loop):
         """
         Unbinding a previously bound variable causes it to be removed from the
-        result of merge_contextvars_context.
+        result of merge_contextvars.
         """
 
         async def coro():
             bind_contextvars(a=1)
             unbind_contextvars("a")
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         assert {"b": 2} == await event_loop.create_task(coro())
 
@@ -130,6 +130,6 @@ class TestNewContextvars(object):
 
         async def coro():
             unbind_contextvars("a")
-            return merge_contextvars_context(None, None, {"b": 2})
+            return merge_contextvars(None, None, {"b": 2})
 
         assert {"b": 2} == await event_loop.create_task(coro())
diff --git a/tests/test_threadlocal.py b/tests/test_threadlocal.py
index 6661ccb..c986723 100644
--- a/tests/test_threadlocal.py
+++ b/tests/test_threadlocal.py
@@ -17,6 +17,7 @@ from structlog.threadlocal import (
     as_immutable,
     bind_threadlocal,
     clear_threadlocal,
+    merge_threadlocal,
     merge_threadlocal_context,
     tmp_bind,
     wrap_dict,
@@ -272,33 +273,37 @@ class TestThreadLocalDict(object):
 
 
 class TestNewThreadLocal(object):
+    def test_alias(self):
+        """
+        We're keeping the old alias around.
+        """
+        assert merge_threadlocal_context is merge_threadlocal
+
     def test_bind_and_merge(self):
         """
         Binding a variable causes it to be included in the result of
-        merge_threadlocal_context.
+        merge_threadlocal.
         """
         bind_threadlocal(a=1)
 
-        assert {"a": 1, "b": 2} == merge_threadlocal_context(
-            None, None, {"b": 2}
-        )
+        assert {"a": 1, "b": 2} == merge_threadlocal(None, None, {"b": 2})
 
     def test_clear(self):
         """
         The thread-local context can be cleared, causing any previously bound
-        variables to not be included in merge_threadlocal_context's result.
+        variables to not be included in merge_threadlocal's result.
         """
         bind_threadlocal(a=1)
         clear_threadlocal()
 
-        assert {"b": 2} == merge_threadlocal_context(None, None, {"b": 2})
+        assert {"b": 2} == merge_threadlocal(None, None, {"b": 2})
 
     def test_merge_works_without_bind(self):
         """
-        merge_threadlocal_context returns values as normal even when there has
+        merge_threadlocal returns values as normal even when there has
         been no previous calls to bind_threadlocal.
         """
-        assert {"b": 2} == merge_threadlocal_context(None, None, {"b": 2})
+        assert {"b": 2} == merge_threadlocal(None, None, {"b": 2})
 
     def test_multiple_binds(self):
         """
@@ -308,6 +313,6 @@ class TestNewThreadLocal(object):
         bind_threadlocal(a=1, b=2)
         bind_threadlocal(c=3)
 
-        assert {"a": 1, "b": 2, "c": 3} == merge_threadlocal_context(
+        assert {"a": 1, "b": 2, "c": 3} == merge_threadlocal(
             None, None, {"b": 2}
         )