Repository: meltano/sdk
Pull request #1238 (2022-12-02 23:02:37)

fix(taps): Ensure metric tags coming from stream context can be JSON-serialized
Otherwise adding objects in a streams `context` that can't be JSON-serialized cause the tap to fail.

For example, pendulum objects in [tap-lastfm](https://github.com/rabidaudio/tap-lastfm/blob/606b4608355a37609e81be0827d2283f4cb94cd4/tap_lastfm/streams.py#L58-L63).

Closes https://github.com/meltano/sdk/issues/1239

edgarrmondragon: cc @rabidaudio this is blocking tap-lastfm from working with the latest SDK.
codecov[bot]: # [Codecov](https://codecov.io/gh/meltano/sdk/pull/1238?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meltano) Report
> Merging [#1238](https://codecov.io/gh/meltano/sdk/pull/1238?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meltano) (1888f57) into [main](https://codecov.io/gh/meltano/sdk/commit/cf51acc6debf608da12ffbeef99aacc0a43e5a99?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meltano) (cf51acc) will **not change** coverage.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##             main    #1238   +/-   ##
=======================================
  Coverage   83.72%   83.72%           
=======================================
  Files          42       42           
  Lines        3909     3909           
  Branches      665      665           
=======================================
  Hits         3273     3273           
  Misses        473      473           
  Partials      163      163           
```


| [Impacted Files](https://codecov.io/gh/meltano/sdk/pull/1238?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meltano) | Coverage Δ | |
|---|---|---|
| [singer\_sdk/metrics.py](https://codecov.io/gh/meltano/sdk/pull/1238/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meltano#diff-c2luZ2VyX3Nkay9tZXRyaWNzLnB5) | `96.03% <100.00%> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=meltano)


Patch:
diff --git a/docs/_templates/plugin_class.rst b/docs/_templates/plugin_class.rst
new file mode 100644
index 00000000..f0f50324
--- /dev/null
+++ b/docs/_templates/plugin_class.rst
@@ -0,0 +1,10 @@
+{{ fullname }}
+{{ "=" * fullname|length }}
+
+.. currentmodule:: {{ module }}
+
+.. autoclass:: {{ name }}
+    :members:
+    :show-inheritance:
+    :inherited-members:
+    :special-members: __init__
diff --git a/docs/classes/singer_sdk.InlineMapper.rst b/docs/classes/singer_sdk.InlineMapper.rst
index 978ef7fb..a6e8b5e7 100644
--- a/docs/classes/singer_sdk.InlineMapper.rst
+++ b/docs/classes/singer_sdk.InlineMapper.rst
@@ -5,4 +5,6 @@
 
 .. autoclass:: InlineMapper
     :members:
+    :show-inheritance:
+    :inherited-members:
     :special-members: __init__
\ No newline at end of file
diff --git a/docs/classes/singer_sdk.SQLTap.rst b/docs/classes/singer_sdk.SQLTap.rst
index 1c49d105..84433750 100644
--- a/docs/classes/singer_sdk.SQLTap.rst
+++ b/docs/classes/singer_sdk.SQLTap.rst
@@ -5,4 +5,6 @@
 
 .. autoclass:: SQLTap
     :members:
+    :show-inheritance:
+    :inherited-members:
     :special-members: __init__
\ No newline at end of file
diff --git a/docs/classes/singer_sdk.SQLTarget.rst b/docs/classes/singer_sdk.SQLTarget.rst
index 36c32244..5c16f3f3 100644
--- a/docs/classes/singer_sdk.SQLTarget.rst
+++ b/docs/classes/singer_sdk.SQLTarget.rst
@@ -5,4 +5,6 @@
 
 .. autoclass:: SQLTarget
     :members:
+    :show-inheritance:
+    :inherited-members:
     :special-members: __init__
\ No newline at end of file
diff --git a/docs/classes/singer_sdk.Tap.rst b/docs/classes/singer_sdk.Tap.rst
index ae4cb051..cd6702fb 100644
--- a/docs/classes/singer_sdk.Tap.rst
+++ b/docs/classes/singer_sdk.Tap.rst
@@ -5,4 +5,6 @@
 
 .. autoclass:: Tap
     :members:
+    :show-inheritance:
+    :inherited-members:
     :special-members: __init__
\ No newline at end of file
diff --git a/docs/classes/singer_sdk.Target.rst b/docs/classes/singer_sdk.Target.rst
index c6aabb5a..15347935 100644
--- a/docs/classes/singer_sdk.Target.rst
+++ b/docs/classes/singer_sdk.Target.rst
@@ -5,4 +5,6 @@
 
 .. autoclass:: Target
     :members:
+    :show-inheritance:
+    :inherited-members:
     :special-members: __init__
\ No newline at end of file
diff --git a/docs/reference.rst b/docs/reference.rst
index ec89e5ec..53cdd9e0 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -8,7 +8,7 @@ Plugin Classes
 
 .. autosummary::
     :toctree: classes
-    :template: class.rst
+    :template: plugin_class.rst
 
     Tap
     Target
diff --git a/singer_sdk/metrics.py b/singer_sdk/metrics.py
index b7f2da02..d6edb287 100644
--- a/singer_sdk/metrics.py
+++ b/singer_sdk/metrics.py
@@ -77,7 +77,7 @@ class Point(Generic[_TVal]):
         Returns:
             A JSON object.
         """
-        return json.dumps(asdict(self))
+        return json.dumps(asdict(self), default=str)
 
 
 def log(logger: logging.Logger, point: Point) -> None:
diff --git a/singer_sdk/plugin_base.py b/singer_sdk/plugin_base.py
index 561e4c5c..feb0f0db 100644
--- a/singer_sdk/plugin_base.py
+++ b/singer_sdk/plugin_base.py
@@ -275,7 +275,9 @@ class PluginBase(metaclass=abc.ABCMeta):
 
         Args:
             print_fn: A function to use to display the plugin version.
-                Defaults to :function:`print`.
+                Defaults to `print`_.
+
+        .. _print: https://docs.python.org/3/library/functions.html#print
         """
         print_fn(f"{cls.name} v{cls.plugin_version}, Meltano SDK v{cls.sdk_version}")
 


Test patch:
diff --git a/tests/core/test_metrics.py b/tests/core/test_metrics.py
index 7bd8c748..d7ce590b 100644
--- a/tests/core/test_metrics.py
+++ b/tests/core/test_metrics.py
@@ -1,11 +1,21 @@
 import logging
 import time
+from textwrap import dedent
 
 import pytest
 
 from singer_sdk import metrics
 
 
+class CustomObject:
+    def __init__(self, name: str, value: int):
+        self.name = name
+        self.value = value
+
+    def __str__(self) -> str:
+        return f"{self.name}={self.value}"
+
+
 def test_meter():
     class _MyMeter(metrics.Meter):
         def __enter__(self):
@@ -28,10 +38,13 @@ def test_meter():
 
 def test_record_counter(caplog: pytest.LogCaptureFixture):
     caplog.set_level(logging.INFO, logger=metrics.METRICS_LOGGER_NAME)
+    custom_object = CustomObject("test", 1)
+
     with metrics.record_counter(
         "test_stream",
         endpoint="test_endpoint",
         custom_tag="pytest",
+        custom_obj=custom_object,
     ) as counter:
         for _ in range(100):
             counter.last_log_time = 0
@@ -46,6 +59,7 @@ def test_record_counter(caplog: pytest.LogCaptureFixture):
     for record in caplog.records:
         assert record.levelname == "INFO"
         assert record.msg == "INFO METRIC: %s"
+        assert "test=1" in record.message
 
         point: metrics.Point[int] = record.args[0]
         assert point.metric_type == "counter"
@@ -54,6 +68,7 @@ def test_record_counter(caplog: pytest.LogCaptureFixture):
             metrics.Tag.STREAM: "test_stream",
             metrics.Tag.ENDPOINT: "test_endpoint",
             "custom_tag": "pytest",
+            "custom_obj": custom_object,
         }
 
         total += point.value