From 4a5db628097816982e5070679b8af25a6261f385 Mon Sep 17 00:00:00 2001 From: stringertheory Date: Mon, 5 Aug 2024 20:44:50 -0500 Subject: [PATCH] remove external inf dependency --- docs-md/index.md | 1 + docs-md/modules.md | 5 ++ docs-md/stylesheets/extra.css | 3 + mkdocs.yml | 2 + poetry.lock | 28 +++---- pyproject.toml | 2 +- tests/test_utils.py | 2 +- traces/__init__.py | 3 +- traces/infinity.py | 154 ++++++++++++++++++++++++++++++++++ traces/timeseries.py | 72 ++++++++++++---- traces/utils.py | 2 +- 11 files changed, 237 insertions(+), 37 deletions(-) create mode 100644 docs-md/index.md create mode 100644 docs-md/modules.md create mode 100644 docs-md/stylesheets/extra.css create mode 100644 traces/infinity.py diff --git a/docs-md/index.md b/docs-md/index.md new file mode 100644 index 0000000..56c9b00 --- /dev/null +++ b/docs-md/index.md @@ -0,0 +1 @@ +# boodle doodle diff --git a/docs-md/modules.md b/docs-md/modules.md new file mode 100644 index 0000000..dbe6355 --- /dev/null +++ b/docs-md/modules.md @@ -0,0 +1,5 @@ +::: traces.timeseries + +::: traces.timeseries.TimeSeries +:docstring: +:members: diff --git a/docs-md/stylesheets/extra.css b/docs-md/stylesheets/extra.css new file mode 100644 index 0000000..710ce3d --- /dev/null +++ b/docs-md/stylesheets/extra.css @@ -0,0 +1,3 @@ +:root > * { + --md-mermaid-label-bg-color: rgba(255, 255, 255, 0.9); +} diff --git a/mkdocs.yml b/mkdocs.yml index 8373982..f88225f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,5 @@ site_name: traces +docs_dir: ./docs-md repo_url: https://github.com/stringertheory/traces site_url: https://stringertheory.github.io/traces site_description: A Python library for unevenly-spaced time series analysis @@ -48,6 +49,7 @@ extra: link: https://pypi.org/project/traces markdown_extensions: + - mkautodoc - toc: permalink: true - pymdownx.arithmatex: diff --git a/poetry.lock b/poetry.lock index 9592505..f885358 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1091,19 +1091,6 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] -[[package]] -name = "infinity" -version = "1.5" -description = "All-in-one infinity value for Python. Can be compared to any object." -optional = false -python-versions = "*" -files = [ - {file = "infinity-1.5.tar.gz", hash = "sha256:8daa7c15ce2100fdccfde212337e0cd5cf085869f54dc2634b6c30d61461ecda"}, -] - -[package.extras] -test = ["Pygments (>=1.2)", "flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)", "six (>=1.4.1)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1919,6 +1906,19 @@ files = [ {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, ] +[[package]] +name = "mkautodoc" +version = "0.2.0" +description = "AutoDoc for MarkDown" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mkautodoc-0.2.0.tar.gz", hash = "sha256:b6e0c89804ba39d453ce5795eb040e5615756f90438ac04f47e97893a86a10cd"}, +] + +[package.dependencies] +Markdown = "*" + [[package]] name = "mkdocs" version = "1.5.3" @@ -4447,4 +4447,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "6b78777677526c41d20fc7303321f99491f082b52fd545c4c848b7d6b8625468" +content-hash = "b97188062ad184ea7a761f3bf4a28bf664626e1faf28f949e2f558c224e97037" diff --git a/pyproject.toml b/pyproject.toml index 9e67082..bdec342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8,<4.0" -infinity = "^1.5" sortedcontainers = "^2.4.0" [tool.poetry.group.dev.dependencies] @@ -46,6 +45,7 @@ bump-my-version = "^0.17.3" notebook = "^7.0.7" deptry = "^0.12.0" python-dateutil = "^2.8.2" +mkautodoc = "^0.2.0" [tool.poetry.group.docs.dependencies] mkdocs = "^1.4.2" diff --git a/tests/test_utils.py b/tests/test_utils.py index 57d107d..b83b36d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,9 @@ from datetime import date, datetime, timedelta import pytest -from infinity import inf import traces.utils as utils +from traces.infinity import inf timedelta_list = [ timedelta(hours=1), diff --git a/traces/__init__.py b/traces/__init__.py index 022b2cd..f4917a7 100644 --- a/traces/__init__.py +++ b/traces/__init__.py @@ -1,8 +1,7 @@ -from infinity import inf - from . import decorators, operations from .eventseries import EventSeries from .histogram import Histogram +from .infinity import inf from .timeseries import TimeSeries from .utils import datetime_range diff --git a/traces/infinity.py b/traces/infinity.py new file mode 100644 index 0000000..104a137 --- /dev/null +++ b/traces/infinity.py @@ -0,0 +1,154 @@ +"""This module is derived from the `infinity` package (at +https://pypi.org/project/infinity/ or +https://github.com/kvesteri/infinity), which is licensed under the BSD +3-Clause "New" or "Revised" License: + +Copyright (c) 2014, Konsta Vesterinen +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + +from functools import total_ordering + + +@total_ordering +class Infinity: + def __init__(self, positive=True): + self.positive = positive + + def __neg__(self): + return Infinity(not self.positive) + + def __gt__(self, other): + if self == other: + return False + return self.positive + + def __eq__(self, other): + return ( + ( + isinstance(other, self.__class__) + and other.positive == self.positive + ) + or (self.positive and other == float("inf")) + or (not self.positive and other == float("-inf")) + ) + + def __ne__(self, other): + return not (self == other) + + def __bool__(self): + return True + + def __nonzero__(self): + return True + + def __str__(self): + return "%sinf" % ("" if self.positive else "-") + + def __float__(self): + return float(str(self)) + + def __add__(self, other): + if is_infinite(other) and other != self: + return NotImplemented + return self + + def __radd__(self, other): + return self + + def __sub__(self, other): + if is_infinite(other) and other == self: + return NotImplemented + return self + + def __rsub__(self, other): + return self + + def timetuple(self): + return () + + def __abs__(self): + return self.__class__() + + def __pos__(self): + return self + + def __div__(self, other): + if is_infinite(other): + return NotImplemented + + return Infinity( + other > 0 and self.positive or other < 0 and not self.positive + ) + + def __rdiv__(self, other): + return 0 + + def __repr__(self): + return str(self) + + __truediv__ = __div__ + __rtruediv__ = __rdiv__ + __floordiv__ = __div__ + __rfloordiv__ = __rdiv__ + + def __mul__(self, other): + if other == 0: + return NotImplemented + return Infinity( + other > 0 and self.positive or other < 0 and not self.positive + ) + + __rmul__ = __mul__ + + def __pow__(self, other): + if other == 0: + return NotImplemented + elif other == -self: + return -0.0 if not self.positive else 0.0 + else: + return Infinity() + + def __rpow__(self, other): + if other in (1, -1): + return NotImplemented + elif other == -self: + return -0.0 if not self.positive else 0.0 + else: + return Infinity() + + def __hash__(self): + return (self.__class__, self.positive).__hash__() + + +inf = Infinity() + + +def is_infinite(value): + return value == inf or value == -inf diff --git a/traces/timeseries.py b/traces/timeseries.py index 24d09d4..832393c 100644 --- a/traces/timeseries.py +++ b/traces/timeseries.py @@ -11,10 +11,9 @@ import itertools from queue import PriorityQueue -from infinity import inf from sortedcontainers import SortedDict -from . import histogram, operations, plot, utils +from . import histogram, infinity, operations, plot, utils NotGiven = object() @@ -164,9 +163,36 @@ def set(self, time, value, compact=False): self._d[time] = value def set_interval(self, start, end, value, compact=False): - """Set the value for the time series on an interval. If compact is - True, only set the value if it's different from what it would - be anyway. + """Sets the value for the time series within a specified time + interval. + + Args: + + start: The start time of the interval, inclusive + end: The end time of the interval, exclusive. + value: The value to set within the interval. + compact (optional): If compact is True, only set the value + if it's different from what it would be anyway. Defaults + to False. + + Raises: + + ValueError: If the start time is equal or after the end + time, indicating an invalid interval. + + Example: + + >>> ts = TimeSeries(data=[(1, 5), (3, 2), (5, 4), (6, 1)]) + >>> ts.set_interval(2, 6, 3) + >>> ts + TimeSeries({1: 5, 2: 3, 6: 1}) + + Note: + + The method sets the value over the interval by removing + measurements points from the time series between start and + end (exclusive), rather than changing the value of any + intermediate points to equal the value. """ if start >= end: @@ -183,13 +209,23 @@ def set_interval(self, start, end, value, compact=False): self.set(end, end_value, compact) def compact(self): - """Convert this instance to a compact version: the value will be the - same at all times, but repeated measurements are discarded. + """Convert this instance to a "compact" version: the value + will be the same at all times, but repeated measurements are + discarded. - """ + Compacting the time series can significantly reduce the length + and memory usage for data with many repeated values. + + No arguments are required for this method, and it modifies the + time series in place. - # todo: change to to_compact, do not modify in place. mark as deprecated + Example: + >>> ts = TimeSeries(data=[(1, 5), (2, 5), (5, 5), (6, 1)]) + >>> ts.compact() + >>> ts + TimeSeries({1: 5, 6: 1}) + """ previous_value = object() redundant = [] for time, value in self: @@ -255,10 +291,8 @@ def __repr__(self): def format_item(item): return "{!r}: {!r}".format(*item) - return "{name}({{{items}}})".format( - name=type(self).__name__, - items=", ".join(format_item(_) for _ in self._d.items()), - ) + items = dict(self._d.items()) + return f"{type(self).__name__}(default={self.default!r}, {items!r})" def __str__(self): """A human-readable string representation (truncated if it gets too @@ -284,7 +318,9 @@ def format_item(item): else: items = ", ".join(format_item(_) for _ in self._d.items()) - return f"{type(self).__name__}({{{items}}})" + return f"{type(self).__name__}(default={self.default!r}, {{{items}}})" + + # return f"{type(self).__name__}({{{items}}})" def iterintervals(self, n=2): """Iterate over groups of `n` consecutive measurement points in the @@ -781,8 +817,8 @@ def distribution( def n_points( self, - start=-inf, - end=+inf, + start=-infinity.inf, + end=+infinity.inf, mask=None, include_start=True, include_end=False, @@ -1165,10 +1201,10 @@ def __ne__(self, other): def _check_boundary(self, value, allow_infinite, lower_or_upper): if lower_or_upper == "lower": - infinity_value = -inf + infinity_value = -infinity.inf method_name = "first_key" elif lower_or_upper == "upper": - infinity_value = inf + infinity_value = infinity.inf method_name = "last_key" else: msg = ( diff --git a/traces/utils.py b/traces/utils.py index 6d082a4..bae70bb 100644 --- a/traces/utils.py +++ b/traces/utils.py @@ -2,7 +2,7 @@ import datetime import numbers -from infinity import inf +from .infinity import inf def duration_to_number(duration, units="seconds"):