From b9bd62d39478f19cc0c7b22ebecd729db785a09e Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Sun, 31 May 2020 13:22:54 +0000 Subject: [PATCH] Library version 1.0 --- README.md | 20 +++++ __init__.py | 5 ++ core/__init__.py | 1 + core/errors.py | 13 ++++ core/structures.py | 190 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 23 ++++++ 6 files changed, 252 insertions(+) create mode 100644 README.md create mode 100644 __init__.py create mode 100644 core/__init__.py create mode 100644 core/errors.py create mode 100644 core/structures.py create mode 100644 setup.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..df4c00e --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# ttlcollections - Pure python collections with Time-to-live + +`ttlcollections` is a pure Python implementation of a various data structures with built-in TTL (time to live) functionality, using nothing but the standard library. + +## Usage + +Coming soon + +## Installation + +Coming soon + +## Credits + +Coming soon + + +## FAQs + +Coming soon diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..758f0bc --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +from .core.structures import TTLQueue, TTLStack +from .core.errors import QueueEmpty, StackEmpty, QueueFull, StackFull + +__all__ = ["TTLQueue", "TTLStack", "QueueEmpty", "QueueFull", "StackEmpty", "StackFull"] +__version__ = (1, 0, 0) diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..8dcd085 --- /dev/null +++ b/core/__init__.py @@ -0,0 +1 @@ +__version__ = (1, 0, 0) diff --git a/core/errors.py b/core/errors.py new file mode 100644 index 0000000..59ad972 --- /dev/null +++ b/core/errors.py @@ -0,0 +1,13 @@ +class QueueFull(Exception): + """This error is raised when a queue is full""" + + +class QueueEmpty(Exception): + """This error is raised when a queue is empty""" + + +class StackEmpty(Exception): + """This error is raised when a stack is empty""" + +class StackFull(Exception): + """This error is raised when a stack is full""" diff --git a/core/structures.py b/core/structures.py new file mode 100644 index 0000000..9f61469 --- /dev/null +++ b/core/structures.py @@ -0,0 +1,190 @@ +from collections import deque +from time import monotonic +from types import FunctionType +import math +from .errors import QueueEmpty, QueueFull, StackEmpty, StackFull + +class TTLQueue: + + """A FIFO queue with per-item time to live (TTL) + All items will have a default time to live, after that has + expired (on the next mutating operation a.k.a put or get) + expired elements will be popped out automatically. + It is also possible to set a different TTL for every item and to + define the maximum queue size + + Note: This queue is NOT thread safe and must be properly locked + when used with multiple threads + + :param qsize: The max size of the queue, defaults to 0 (no limit) + :type qsize: int, optional + :param ttl: The TTL for every item in the queue, defaults to 0 (no TTL) + :type ttl: int, optional + :param timer: The timer function that the queue will use to + keep track of elapsed time. Defaults to time.monotonic(), but can + be customized (another monotonic clock is timeit.default_timer, for + instance). Any function that yields an incremental value + on each subsequent call is acceptable, but its return values + should not be repeated during runtime to avoid nonsense results + :type timer: class: FunctionType, optional + """ + + def __init__(self, qsize: int = 0, ttl: int = 0, timer: FunctionType = monotonic): + """Object constructor""" + + self.qsize = qsize if qsize else math.inf # Infinite size + self.ttl = ttl + self.timer = timer + self._queue = deque() + + def expire(self, when: int): + """Pops expired element out of the queue if their TTL has + expired by when units of time (usually seconds) + + :param when: The expiry date to check items against. Items' whose + insertion date, according to self.timer, is less or equal + than this number will be automatically deleted + :type when: int + """ + + i = 0 + n = len(self._queue) + while i < n: + try: + date, element = self._queue[i] + except IndexError: + break + if date <= when: + del self._queue[i] + i += 1 + + def put(self, element, ttl: int = 0): + """Puts an item onto the queue + + :param element: The element to put in the queue + :type element: object + :param ttl: If you want to override the default ttl + of the class for a specific element, you can specify + that, defaults to 0 (use the default TTL) + :param ttl: int, optional + :raises QueueFull: If the queue is full + """ + + ttl = ttl if ttl else self.ttl + self.expire(self.timer()) + if len(self._queue) < self.qsize: + self._queue.append((self.timer() + ttl, element)) + else: + raise QueueFull("The queue is full!") + + def get(self): + """Gets an item from the queue, raising QueueEmpty if the + queue is empty + """ + + self.expire(self.timer()) + if not self._queue: + raise QueueEmpty("The queue is empty!") + return self._queue.popleft()[1] + + def __repr__(self): + """Implements repr(self)""" + + string = "TTLQueue({list}, qsize={qsize}, ttl={ttl}, timer={timer})" + values = [t[1] for t in self._queue] + return string.format(list=values, qsize=self.qsize, ttl=self.ttl, timer=self.timer) + + def __iter__(self): + """Implements iter(self)""" + + for _, element in self._queue: + yield element + + +class TTLStack(TTLQueue): + """A stack-like (LIFO) data structure with per-item time to live (TTL) + All items will have a default time to live, after that has + expired (on the next mutating operation a.k.a push or pop) + expired elements will be popped out automatically. + It is also possible to set a different TTL for every item and to + define the maximum stack + + Note: This stack is NOT thread safe and must be properly locked + when used with multiple threads + + :param qsize: The max size of the stack, defaults to 0 (no limit) + :type qsize: int, optional + :param ttl: The TTL for every item in the stack, defaults to 0 (no TTL) + :type ttl: int, optional + :param timer: The timer function that the stack will use to + keep track of elapsed time. Defaults to time.monotonic(), but can + be customized (another monotonic clock is timeit.default_timer, for + instance). Any function that yields an incremental value + on each subsequent call is acceptable, but its return values + should not be repeated during runtime to avoid nonsense results + :type timer: class: FunctionType, optional + """ + + def __init__(self, qsize: int = 0, ttl: int = 0, timer: FunctionType = monotonic): + """Object constructor""" + + super().__init__(qsize, ttl, timer) + self._stack = deque() + + def push(self, element, ttl: int = 0): + """Pushes an item onto the stack + + :param element: The element to push + :type element: object + :param ttl: If you want to override the default ttl + of the class for a specific element, you can specify + that, defaults to 0 (use the default TTL) + :param ttl: int, optional + :raises StackFull: If the stack is full + """ + + ttl = ttl if ttl else self.ttl + self.expire(self.timer()) + if len(self._stack) < self.qsize: + self._stack.appendleft((self.timer() + ttl, element)) + else: + raise StackFull("The stack is full!") + + def pop(self): + """Pops an item from the stack, raising StackEmpty if the + stack is empty + """ + + self.expire(self.timer()) + if not self._stack: + raise StackEmpty("The stack is empty!") + return self._stack.popleft()[1] + + def __repr__(self): + """Implements repr(self)""" + + string = "TTLStack({list}, qsize={qsize}, ttl={ttl}, timer={timer})" + values = [t[1] for t in self._stack] + return string.format(list=values, qsize=self.qsize, ttl=self.ttl, timer=self.timer) + + def expire(self, when: int): + """Pops expired element out of the stack if their TTL has + expired by when units of time (usually seconds) + + :param when: The expiry date to check items against. Items' whose + insertion date, according to self.timer, is less or equal + than this number will be automatically deleted + :type when: int + """ + + i = 0 + n = len(self._stack) + while i < n: + try: + date, element = self._stack[i] + except IndexError: + break + if date <= when: + del self._stack[i] + i += 1 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0513de3 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +from setuptools import setup, find_packages + + +with open("README.md", "r") as fh: + long_description = fh.read() + +setup( + name="ttlcollections", + version="1.0", + author="Nocturn9x", + author_email="nocturn9x@intellivoid.net", + description="Pure python collections with TTL", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/nocturn9x/ttlqueue", + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU Lesser General Public License v3.0" + ], + python_requires='>=3.6', +)