Skip to content

Best import and typing #251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/pytest_benchmark/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
from pytest_benchmark.fixture import BenchmarkFixture
from pytest_benchmark.stats import Stats

Check warning on line 2 in src/pytest_benchmark/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/__init__.py#L1-L2

Added lines #L1 - L2 were not covered by tests

__version__ = '4.0.0'
10 changes: 8 additions & 2 deletions src/pytest_benchmark/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import time
import traceback
from math import ceil
from typing import Any

Check warning on line 11 in src/pytest_benchmark/fixture.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/fixture.py#L11

Added line #L11 was not covered by tests

from .timers import compute_timer_precision
from .utils import NameWrapper
from .utils import NameWrapper, cached_property

Check warning on line 14 in src/pytest_benchmark/fixture.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/fixture.py#L14

Added line #L14 was not covered by tests
from .utils import format_time

try:
Expand All @@ -20,7 +21,7 @@
statistics = None
else:
statistics_error = None
from .stats import Metadata
from .stats import Metadata, Stats

Check warning on line 24 in src/pytest_benchmark/fixture.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/fixture.py#L24

Added line #L24 was not covered by tests


class FixtureAlreadyUsed(Exception):
Expand Down Expand Up @@ -66,6 +67,11 @@
def enabled(self):
return not self.disabled

@property
def statistics(self) -> Stats:

Check warning on line 71 in src/pytest_benchmark/fixture.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/fixture.py#L70-L71

Added lines #L70 - L71 were not covered by tests
"""Make statistics of the benchmarked function easier to access."""
return self.stats.stats

def _get_precision(self, timer):
if timer in self._precisions:
timer_precision = self._precisions[timer]
Expand Down
256 changes: 217 additions & 39 deletions src/pytest_benchmark/stats.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""
Don't use cached_property because we want a typing for getter and setter,
and we can't use iqr_outliers.setter with cached_property because Python apply the
decorator on cached_property and not on the property.
"""

from __future__ import division
from __future__ import print_function

Expand All @@ -6,9 +12,9 @@
from bisect import bisect_left
from bisect import bisect_right

from .utils import cached_property
from .utils import funcname
from .utils import get_cprofile_functions
from .utils import cached_property

Check warning on line 17 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L17

Added line #L17 was not covered by tests


class Stats(object):
Expand All @@ -19,6 +25,7 @@

def __init__(self):
self.data = []
self.cache = {}

def __bool__(self):
return bool(self.data)
Expand All @@ -39,34 +46,97 @@
def sorted_data(self):
return sorted(self.data)

@cached_property
def total(self):
@property
def total(self) -> float or int:

Check warning on line 50 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L49-L50

Added lines #L49 - L50 were not covered by tests
""" Return the total time of round / iterations."""
cache_value = self.cache.get('total')

if cache_value is not None:
return cache_value

return sum(self.data)

@cached_property
def min(self):
@total.setter
def total(self, value: float or int) -> None:

Check warning on line 60 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L59-L60

Added lines #L59 - L60 were not covered by tests
""" Set the total time of round / iterations."""
self.cache['total'] = value

@property
def min(self) -> float or int:

Check warning on line 65 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L64-L65

Added lines #L64 - L65 were not covered by tests
""" Return the minimum observed time of round / iterations."""
cache_value = self.cache.get('min')

if cache_value is not None:
return cache_value

return min(self.data)

@cached_property
def max(self):
@min.setter
def min(self, value: float or int) -> None:

Check warning on line 75 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L74-L75

Added lines #L74 - L75 were not covered by tests
""" Set the minimum observed time of round / iterations."""
self.cache['min'] = value

@property
def max(self) -> float or int:

Check warning on line 80 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L79-L80

Added lines #L79 - L80 were not covered by tests
""" Return the maximum observed time of round / iterations"""
cache_value = self.cache.get('max')

if cache_value is not None:
return cache_value

return max(self.data)

@cached_property
def mean(self):
@max.setter
def max(self, value: float or int) -> None:

Check warning on line 90 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L89-L90

Added lines #L89 - L90 were not covered by tests
""" Set the maximum observed time of round / iterations."""
self.cache['max'] = value

@property
def mean(self) -> float or int:

Check warning on line 95 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L94-L95

Added lines #L94 - L95 were not covered by tests
""" Return the mean of time of round / iterations."""
cache_value = self.cache.get('mean')

if cache_value is not None:
return cache_value

return statistics.mean(self.data)

@cached_property
def stddev(self):
@mean.setter
def mean(self, value: float or int) -> None:

Check warning on line 105 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L104-L105

Added lines #L104 - L105 were not covered by tests
""" Set the mean. """
self.cache['mean'] = value

@property
def stddev(self) -> float or int:

Check warning on line 110 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L109-L110

Added lines #L109 - L110 were not covered by tests
""" Return the standard deviation. """
cache_value = self.cache.get('stddev')

if cache_value is not None:
return cache_value

if len(self.data) > 1:
return statistics.stdev(self.data)
else:
return 0

@stddev.setter
def stddev(self, value: float or int) -> None:

Check warning on line 123 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L122-L123

Added lines #L122 - L123 were not covered by tests
""" Set the standard deviation. """
self.cache['stddev'] = value

@property
def stddev_outliers(self):
def stddev_outliers(self) -> float or int:

Check warning on line 128 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L128

Added line #L128 was not covered by tests
"""
Count of StdDev outliers: what's beyond (Mean - StdDev, Mean - StdDev)
Return the number of outliers (StdDev-style).

Notes:
Count of StdDev outliers: what's beyond (Mean - StdDev, Mean - StdDev)
"""
cache_value = self.cache.get('stddev_outliers')

if cache_value is not None:
return cache_value

count = 0
q0 = self.mean - self.stddev
q4 = self.mean + self.stddev
Expand All @@ -75,29 +145,57 @@
count += 1
return count

@cached_property
def rounds(self):
@stddev_outliers.setter
def stddev_outliers(self, value: str) -> None:

Check warning on line 149 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L148-L149

Added lines #L148 - L149 were not covered by tests
""" Set the number of outliers. """
self.cache['stddev_outliers'] = value

@property
def rounds(self) -> float or int:

Check warning on line 154 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L153-L154

Added lines #L153 - L154 were not covered by tests
""" Return the number of rounds, can't be changed by setter."""
return len(self.data)

@cached_property
def median(self):
@property
def median(self) -> float or int:

Check warning on line 159 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L158-L159

Added lines #L158 - L159 were not covered by tests
""" Return the median of time of round / iterations. """
cache_value = self.cache.get('median')

if cache_value is not None:
return cache_value

return statistics.median(self.data)

@cached_property
def ld15iqr(self):
"""
Tukey-style Lowest Datum within 1.5 IQR under Q1.
"""
@median.setter
def median(self, value: float or int) -> None:

Check warning on line 169 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L168-L169

Added lines #L168 - L169 were not covered by tests
""" Set the median of time of round / iterations."""
self.cache['median'] = value

@property
def ld15iqr(self) -> float or int:

Check warning on line 174 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L173-L174

Added lines #L173 - L174 were not covered by tests
""" Return the lowest datum within 1.5 IQR under Q1 (Tukey-style). """
cache_value = self.cache.get('ld15iqr')

if cache_value is not None:
return cache_value

if len(self.data) == 1:
return self.data[0]
else:
return self.sorted_data[bisect_left(self.sorted_data, self.q1 - 1.5 * self.iqr)]

@cached_property
def hd15iqr(self):
"""
Tukey-style Highest Datum within 1.5 IQR over Q3.
"""
@ld15iqr.setter
def ld15iqr(self, value: int or float) -> None:

Check warning on line 187 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L186-L187

Added lines #L186 - L187 were not covered by tests
""" Set the lowest datum within 1.5 IQR under Q1 (Tukey-style). """
self.cache['ld15iqr'] = value

@property
def hd15iqr(self) -> float or int:

Check warning on line 192 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L191-L192

Added lines #L191 - L192 were not covered by tests
""" Return the highest datum within 1.5 IQR over Q3 (Tukey-style). """
cache_value = self.cache.get('hd15iqr')

if cache_value is not None:
return cache_value

if len(self.data) == 1:
return self.data[0]
else:
Expand All @@ -107,8 +205,19 @@
else:
return self.sorted_data[pos]

@cached_property
def q1(self):
@hd15iqr.setter
def hd15iqr(self, value: int or float) -> None:

Check warning on line 209 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L208-L209

Added lines #L208 - L209 were not covered by tests
""" Set the highest datum within 1.5 IQR over Q3 (Tukey-style). """
self.cache['hd15iqr'] = value

@property
def q1(self) -> float or int:

Check warning on line 214 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L213-L214

Added lines #L213 - L214 were not covered by tests
""" Return the first quartile. """
cache_value = self.cache.get('q1')

if cache_value is not None:
return cache_value

rounds = self.rounds
data = self.sorted_data

Expand All @@ -124,8 +233,19 @@
else: # Method 2
return statistics.median(data[:rounds // 2])

@cached_property
def q3(self):
@q1.setter
def q1(self, value: int or float) -> None:

Check warning on line 237 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L236-L237

Added lines #L236 - L237 were not covered by tests
""" Set the first quartile. """
self.cache['q1'] = value

@property
def q3(self) -> float or int:

Check warning on line 242 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L241-L242

Added lines #L241 - L242 were not covered by tests
""" Return the third quartile. """
cache_value = self.cache.get('q3')

if cache_value is not None:
return cache_value

rounds = self.rounds
data = self.sorted_data

Expand All @@ -141,15 +261,39 @@
else: # Method 2
return statistics.median(data[rounds // 2:])

@cached_property
def iqr(self):
@q3.setter
def q3(self, value: int or float) -> None:

Check warning on line 265 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L264-L265

Added lines #L264 - L265 were not covered by tests
""" Set the third quartile. """
self.cache['q3'] = value

@property
def iqr(self) -> float or int:

Check warning on line 270 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L269-L270

Added lines #L269 - L270 were not covered by tests
""" Return the interquartile range. """
cache_value = self.cache.get('iqr')

if cache_value is not None:
return cache_value

return self.q3 - self.q1

@iqr.setter
def iqr(self, value) -> None:

Check warning on line 280 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L279-L280

Added lines #L279 - L280 were not covered by tests
""" Set the interquartile range. """
self.cache['iqr'] = value

@property
def iqr_outliers(self):
def iqr_outliers(self) -> float or int:

Check warning on line 285 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L285

Added line #L285 was not covered by tests
"""
Count of Tukey outliers: what's beyond (Q1 - 1.5IQR, Q3 + 1.5IQR)
Return the number of outliers (Tukey-style).

Notes:
Count of Tukey outliers: what's beyond (Q1 - 1.5IQR, Q3 + 1.5IQR)
"""
cache_value = self.cache.get('iqr_outliers')

if cache_value is not None:
return cache_value

count = 0
q0 = self.q1 - 1.5 * self.iqr
q4 = self.q3 + 1.5 * self.iqr
Expand All @@ -158,16 +302,50 @@
count += 1
return count

@cached_property
def outliers(self):
@iqr_outliers.setter
def iqr_outliers(self, value: str) -> None:

Check warning on line 306 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L305-L306

Added lines #L305 - L306 were not covered by tests
""" Set the number of outliers. """
self.cache['iqr_outliers'] = value

@property
def outliers(self) -> str:

Check warning on line 311 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L310-L311

Added lines #L310 - L311 were not covered by tests
"""
Return the number of outliers.

Notes:
This is a string because it is used in a template.
The separator is a semicolon ';' because it is used in a template.
"""

cache_value = self.cache.get('outliers')

if cache_value is not None:
return cache_value

return "%s;%s" % (self.stddev_outliers, self.iqr_outliers)

@cached_property
def ops(self):
@outliers.setter
def outliers(self, value: str) -> None:

Check warning on line 328 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L327-L328

Added lines #L327 - L328 were not covered by tests
""" Set the number of outliers. """
self.cache['outliers'] = value

@property
def ops(self) -> float or int:

Check warning on line 333 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L332-L333

Added lines #L332 - L333 were not covered by tests
""" Return the average of operations per second of round / iterations."""
cache_value = self.cache.get('ops')

if cache_value is not None:
return cache_value

if self.total:
return self.rounds / self.total
return 0

@ops.setter
def ops(self, value: float or int) -> None:

Check warning on line 345 in src/pytest_benchmark/stats.py

View check run for this annotation

Codecov / codecov/patch

src/pytest_benchmark/stats.py#L344-L345

Added lines #L344 - L345 were not covered by tests
"""Set the average of operations per second of round / iterations."""
self.cache['ops'] = value


class Metadata(object):
def __init__(self, fixture, iterations, options):
Expand Down
Loading