Library version 1.0
This commit is contained in:
parent
d4af635277
commit
b9bd62d394
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = (1, 0, 0)
|
|
@ -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"""
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
)
|
Loading…
Reference in New Issue