Repository: belm0/perf-timer

Issue:
`time.thread_time` is not available on some OS X builds
It appears `time.thread_time` is not available on mac os (https://bugs.python.org/issue32093) and thus trying to use this package will raise `ImportError` 😢  
```python-traceback
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-277-0f19c673cd74> in <module>
----> 1 from perf_timer import PerfTimer, HistogramObserver

~/opt/anaconda3/envs/py37/lib/python3.7/site-packages/perf_timer/__init__.py in <module>
----> 1 from ._impl import (PerfTimer, ThreadPerfTimer, AverageObserver,
      2                     StdDevObserver, HistogramObserver, measure_overhead)
      3 try:
      4     from ._trio import trio_perf_counter, TrioPerfTimer
      5 except ImportError:

~/opt/anaconda3/envs/py37/lib/python3.7/site-packages/perf_timer/_impl.py in <module>
      8 from inspect import iscoroutinefunction
      9 from multiprocessing import Lock
---> 10 from time import perf_counter, thread_time
     11 
     12 from perf_timer._histogram import ApproximateHistogram

ImportError: cannot import name 'thread_time' from 'time' (unknown location)
```

Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f01fb66..3c73103 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Release history
 
+## perf-timer (pending)
+### Fixed
+- handle absence of `time.thread_timer()` gracefully.  This timer, which is the
+  default used by `ThreadPerfTimer`, may not be available in some OS X
+  environments.
+
 ## perf-timer 0.2.1 (2020-11-09)
 ### Fixed
 - employ `atexit()` to robustly log results even when `__del__` finalizers are
diff --git a/src/perf_timer/_impl.py b/src/perf_timer/_impl.py
index e135af1..f8e842a 100644
--- a/src/perf_timer/_impl.py
+++ b/src/perf_timer/_impl.py
@@ -7,7 +7,12 @@ from weakref import WeakSet
 from contextvars import ContextVar
 from inspect import iscoroutinefunction
 from multiprocessing import Lock
-from time import perf_counter, thread_time
+from time import perf_counter
+try:
+    from time import thread_time
+except ImportError:
+    # thread_time is not available in some OS X environments
+    thread_time = None
 
 from perf_timer._histogram import ApproximateHistogram
 
@@ -62,7 +67,8 @@ class _PerfTimerBase(_BetterContextDecorator):
                  observer=None, quantiles=None):
         """
         :param name: string used to annotate the timer output
-        :param time_fn: optional function which returns the current time
+        :param time_fn: optional function which returns the current time.
+            (A None value will raise NotImplementedError.)
         :param log_fn: optional function which records the output string
         :param observer: mixin class to observe and summarize samples
             (AverageObserver|StdDevObserver|HistogramObserver, default StdDevObserver)
@@ -70,6 +76,8 @@ class _PerfTimerBase(_BetterContextDecorator):
             Values must be in range [0..1] and monotonically increasing.
             (default: (0.5, 0.9, 0.98))
         """
+        if not time_fn:
+            raise NotImplementedError
         self.name = name
         self._time_fn = time_fn
         self._log_fn = log_fn
@@ -258,7 +266,11 @@ class PerfTimer(_PerfTimerBase, metaclass=_MixinMeta):
 
 
 class ThreadPerfTimer(_ObservationLock, PerfTimer):
-    """Variant of PerfTimer which measures CPU time of the current thread"""
+    """Variant of PerfTimer which measures CPU time of the current thread
+
+    (Implemented with time.thread_time by default, which may not be available
+    in some OS X environments.)
+    """
 
     def __init__(self, name, time_fn=thread_time, **kwargs):
         super().__init__(name, time_fn=time_fn, **kwargs)


Test patch:
diff --git a/tests/test_perf_timer.py b/tests/test_perf_timer.py
index 09aeda1..6024dba 100644
--- a/tests/test_perf_timer.py
+++ b/tests/test_perf_timer.py
@@ -117,6 +117,11 @@ def test_perf_timer_type():
     assert type(PerfTimer('foo')) is type(PerfTimer('bar'))
 
 
+def test_perf_timer_not_implemented():
+    with pytest.raises(NotImplementedError):
+        PerfTimer('foo', time_fn=None)
+
+
 @patch.object(PerfTimer, '_report_once')
 def test_perf_timer_atexit_and_del(_report_once):
     # atexit and del each cause 1 call to _report_once()