diff --git a/jutl/__init__.py b/jutl/__init__.py index dd56f06..0104410 100644 --- a/jutl/__init__.py +++ b/jutl/__init__.py @@ -1,5 +1,5 @@ # Dunder attributes -__version__ = "0.5.1" +__version__ = "0.5.2" __author__ = "Jordan Welsman" __license__ = "MIT" __copyright__ = "Copyright 2023 Jordan Welsman" diff --git a/jutl/datastructures/__init__.py b/jutl/datastructures/__init__.py index b86d638..8a59d98 100644 --- a/jutl/datastructures/__init__.py +++ b/jutl/datastructures/__init__.py @@ -1,6 +1,7 @@ # Import submodule files so # classes and functions are usable at # 'from jutl.datastructures import _' level. +from .queue import * from .stack import * # Only show functions specified in diff --git a/jutl/datastructures/queue.py b/jutl/datastructures/queue.py new file mode 100644 index 0000000..4cb04b2 --- /dev/null +++ b/jutl/datastructures/queue.py @@ -0,0 +1,198 @@ +# Module imports +from __future__ import annotations +from typing import Any +from jutl.exceptions import EmptyQueueError, FullQueueError +from jutl.formatting import apply + +# External class visibility +__all__ = ['Queue'] + + +class Queue(object): + """ + Class which implements a + first-in-first-out queue object + with queue methods. + """ + def __init__(self, name: str = None, capacity: int = None) -> None: + "Initialization method." + self.name: str = name + self._queue : list = [] + self._capacity: int = capacity + + def __repr__(self) -> str: + """ + Tells the interpreter how + to represent this class. + """ + string = f"{self.__class__.__name__}(" + if self.name is not None: + string += f"{self.name}" + if len(self._queue) > 0: + string += f", " if self.name is not None else "" + string += f"{len(self._queue)}" + if self._capacity is not None: + string += f"/" if len(self) > 0 else ", " if self.name is not None else "" + string += f"{self._capacity}" + string += ")" + return string + + def __call__(self) -> list: + """ + Tells the interpreter what to + do when an object of this + class is called directly. + """ + return self._queue + + def __len__(self) -> int: + """ + Tells the interpreter what to + consider this class' length. + """ + return len(self._queue) + + def __iter__(self) -> iter: + """ + Tells the interpreter what to + iterate over when iterator methods + are called on this class. + """ + raise NotImplementedError("Queues are not iterable.") + + def __eq__(self, other) -> bool: + """ + Tells the interpreter how this class + handles equal operators. + """ + return self._queue == other._queue + + def __ne__(self, other) -> bool: + """ + Tells the interpreter how this class + handles not equal operators. + """ + return self._queue != other._queue + + def __gt__(self, other) -> bool: + """ + Tells the interpreter how this class + handles greater than operators. + """ + return len(self) > len(other) + + def __ge__(self, other) -> bool: + """ + Tells the interpreter how this class + handles greater or equal operators. + """ + return len(self) >= len(other) + + def __lt__(self, other) -> bool: + """ + Tells the interpreter how this class + handles less than operators. + """ + return len(self) < len(other) + + def __le__(self, other) -> bool: + """ + Tells the interpreter how this class + handles less than or equal operators. + """ + return len(self) <= len(other) + + def __add__(self, other) -> list: + """ + Tells the interpreter how to sum these objects. + """ + return self.extend(other=other) + + + def enqueue(self, *args: object) -> None: + """ + Adds an item to the back of the queue. + """ + for item in args: + if self.is_full: + raise FullQueueError("The queue is full.") + else: + self._queue.append(item) + + + def dequeue(self) -> Any: + """ + Removes the item from at front of the queue. + """ + if self.is_empty: + raise EmptyQueueError("The queue is empty.") + else: + dequeued = self.front + self._queue.pop(0) + return dequeued + + + def clear(self) -> None: + """ + Clears the queue. + """ + self._queue.clear() + + + @property + def front(self) -> Any: + """ + Returns the item at the front of + the queue without dequeueing it. + """ + if self.is_empty: + raise EmptyQueueError("The queue is empty.") + else: + return self._queue[0] + + + @property + def rear(self) -> Any: + """ + Returns the item at the rear of + the queue without dequeueing it. + """ + if self.is_empty: + raise EmptyQueueError("The queue is empty.") + else: + return self._queue[-1] + + + @property + def is_empty(self) -> bool: + """ + Returns whether the queue is empty. + """ + if len(self) <= 0: + return True + else: + return False + + + @property + def is_full(self) -> bool: + """ + Returns whether the queue is full. + """ + if self._capacity is not None: + if len(self) >= self._capacity: + return True + else: + return False + else: + return False + + + def extend(self, other: Queue) -> Queue: + """ + Extends this queue with another queue. + """ + for item in other._queue: + self.enqueue(item) + return self + \ No newline at end of file diff --git a/jutl/datastructures/stack.py b/jutl/datastructures/stack.py index 666349f..46e1d93 100644 --- a/jutl/datastructures/stack.py +++ b/jutl/datastructures/stack.py @@ -1,5 +1,7 @@ # Module imports from __future__ import annotations +from typing import Any +from jutl.exceptions import EmptyStackError, FullStackError from jutl.formatting import apply # External class visibility @@ -9,13 +11,14 @@ class Stack(object): """ Class which implements a - first-in; first-out stack object + first-in-last-out stack object with stack methods. """ - def __init__(self, name: str = None): + def __init__(self, name: str = None, capacity: int = None) -> None: "Initialization method." - self.name = name - self._stack = [] + self.name: str = name + self._stack: list = [] + self._capacity: int = capacity def __repr__(self) -> str: """ @@ -106,29 +109,83 @@ def __add__(self, other) -> list: return self.extend(other=other) - def push(self, *args: object): + def push(self, *args: object) -> None: """ Pushes an item onto the stack. """ for item in args: - self._stack.append(item) + if self.is_full: + raise FullStackError("The stack is full.") + else: + self._stack.append(item) - def pop(self) -> object: + def pop(self) -> Any: """ - Removes the last added item from the stack. + Removes the item at the top of the stack. """ - popped = self.top - self._stack.remove(self.top) - return popped + if self.is_empty: + raise EmptyStackError("The stack is empty.") + else: + popped = self.top + self._stack.pop(-1) + return popped + + + def clear(self) -> None: + """ + Clears the stack. + """ + self._stack.clear() @property - def top(self) -> object: - if len(self) > 0: + def top(self) -> Any: + """ + Returns the item at the top of + the stack without popping it. + """ + if self.is_empty: + raise EmptyStackError("The stack is empty.") + else: return self._stack[-1] + + + @property + def bottom(self) -> Any: + """ + Returns the item at the bottom + of the stack without popping it. + """ + if self.is_empty: + raise EmptyStackError("The stack is empty.") + else: + return self._stack[0] + + + @property + def is_empty(self) -> bool: + """ + Returns whether the stack is empty. + """ + if len(self) == 0: + return True + else: + return False + + + @property + def is_full(self) -> bool: + """ + Returns whether the stack is full. + """ + if self._capacity is not None: + if len(self) >= self._capacity: + return True + else: + return False else: - raise IndexError("Stack is empty.") + return False def extend(self, other: Stack) -> Stack: diff --git a/jutl/exceptions/__init__.py b/jutl/exceptions/__init__.py index 3537feb..a6d92bf 100644 --- a/jutl/exceptions/__init__.py +++ b/jutl/exceptions/__init__.py @@ -2,9 +2,13 @@ # classes and functions are usable at # 'from jutl.exceptions import _' level. from .emptypipeline import * +from .emptyqueue import * +from .emptystack import * +from .fullqueue import * +from .fullstack import * from .invalidformatting import * from .missinginput import * # Only show functions specified in # submodule files to the outside world. -__all__ = emptypipeline.__all__, invalidformatting.__all__, missinginput.__all__ +__all__ = emptypipeline.__all__, emptyqueue.__all__, emptystack.__all__, fullqueue.__all__, fullstack.__all__, invalidformatting.__all__, missinginput.__all__ diff --git a/jutl/exceptions/emptypipeline.py b/jutl/exceptions/emptypipeline.py index 166758c..4692fa2 100644 --- a/jutl/exceptions/emptypipeline.py +++ b/jutl/exceptions/emptypipeline.py @@ -1,8 +1,8 @@ # Module imports +from .jutlexception import JutilsException # External class visibility __all__ = ['EmptyPipelineError'] -class EmptyPipelineError(Exception): - def __init__(self, message): - self.message = message \ No newline at end of file +class EmptyPipelineError(JutilsException): + pass \ No newline at end of file diff --git a/jutl/exceptions/emptyqueue.py b/jutl/exceptions/emptyqueue.py new file mode 100644 index 0000000..3235f08 --- /dev/null +++ b/jutl/exceptions/emptyqueue.py @@ -0,0 +1,8 @@ +# Module imports +from .jutlexception import JutilsException + +# External class visibility +__all__ = ['EmptyQueueError'] + +class EmptyQueueError(JutilsException): + pass \ No newline at end of file diff --git a/jutl/exceptions/emptystack.py b/jutl/exceptions/emptystack.py new file mode 100644 index 0000000..04b6a14 --- /dev/null +++ b/jutl/exceptions/emptystack.py @@ -0,0 +1,8 @@ +# Module imports +from .jutlexception import JutilsException + +# External class visibility +__all__ = ['EmptyStackError'] + +class EmptyStackError(JutilsException): + pass \ No newline at end of file diff --git a/jutl/exceptions/fullqueue.py b/jutl/exceptions/fullqueue.py new file mode 100644 index 0000000..62352e1 --- /dev/null +++ b/jutl/exceptions/fullqueue.py @@ -0,0 +1,8 @@ +# Module imports +from .jutlexception import JutilsException + +# External class visibility +__all__ = ['FullQueueError'] + +class FullQueueError(JutilsException): + pass \ No newline at end of file diff --git a/jutl/exceptions/fullstack.py b/jutl/exceptions/fullstack.py new file mode 100644 index 0000000..1847489 --- /dev/null +++ b/jutl/exceptions/fullstack.py @@ -0,0 +1,8 @@ +# Module imports +from .jutlexception import JutilsException + +# External class visibility +__all__ = ['FullStackError'] + +class FullStackError(JutilsException): + pass \ No newline at end of file diff --git a/jutl/exceptions/invalidformatting.py b/jutl/exceptions/invalidformatting.py index 1916473..9821dd7 100644 --- a/jutl/exceptions/invalidformatting.py +++ b/jutl/exceptions/invalidformatting.py @@ -1,8 +1,8 @@ # Module imports +from .jutlexception import JutilsException # External class visibility __all__ = ['InvalidFormattingError'] -class InvalidFormattingError(Exception): - def __init__(self, message): - self.message = message \ No newline at end of file +class InvalidFormattingError(JutilsException): + pass \ No newline at end of file diff --git a/jutl/exceptions/jutlexception.py b/jutl/exceptions/jutlexception.py new file mode 100644 index 0000000..3978ca6 --- /dev/null +++ b/jutl/exceptions/jutlexception.py @@ -0,0 +1,8 @@ +# Module imports + +# External class visibility +__all__ = ['JutilsException'] + +class JutilsException(Exception): + def __init__(self, message): + self.message = message \ No newline at end of file diff --git a/jutl/exceptions/missinginput.py b/jutl/exceptions/missinginput.py index fba6760..8dfb7dd 100644 --- a/jutl/exceptions/missinginput.py +++ b/jutl/exceptions/missinginput.py @@ -1,8 +1,8 @@ # Module imports +from .jutlexception import JutilsException # External class visibility __all__ = ['MissingInputError'] -class MissingInputError(Exception): - def __init__(self, message): - self.message = message \ No newline at end of file +class MissingInputError(JutilsException): + pass \ No newline at end of file diff --git a/setup.py b/setup.py index 0060aef..94281d7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ # Arguments git_name = "jutils" pypi_name = "jutl" -version = "0.5.1" # update __init__.py +version = "0.5.2" # update __init__.py python_version = ">=3.10" # Long description from README.md diff --git a/test/datastructures/test_queue.py b/test/datastructures/test_queue.py new file mode 100644 index 0000000..9cf7901 --- /dev/null +++ b/test/datastructures/test_queue.py @@ -0,0 +1,226 @@ +# jutils/test/datastructures/test_queue.py +from jutl.datastructures import Queue + +test_name = "Test name" +test_item = "Item" + +class TestInit(): + def test_def(self): + "Tests if an object can be created from the Queue class." + queue = Queue() + assert isinstance(queue, Queue) + del(queue) + + def test_name(self): + "Tests if naming a queue works." + queue = Queue() + assert queue.name is None + del(queue) + + queue = Queue(test_name) + assert queue.name == test_name + del(queue) + + +class TestDunder(): + def test_repr(self): + "Tests what is output for representation." + queue = Queue() + assert repr(queue) == f"Queue()" + del(queue) + + queue = Queue(test_name) + assert repr(queue) == f"Queue({test_name})" + del(queue) + + queue = Queue() + queue.enqueue(test_item, test_item, test_item) + assert repr(queue) == f"Queue(3)" + del(queue) + + queue = Queue(capacity=10) + assert repr(queue) == f"Queue(10)" + del(queue) + + queue = Queue(test_name) + queue.enqueue(test_item, test_item, test_item) + assert repr(queue) == f"Queue({test_name}, 3)" + del(queue) + + queue = Queue(test_name, 10) + assert repr(queue) == f"Queue({test_name}, 10)" + del(queue) + + queue = Queue(test_name, 10) + queue.enqueue(test_item, test_item, test_item) + assert repr(queue) == f"Queue({test_name}, 3/10)" + del(queue) + + + def test_len(self): + "Tests what is output for object length." + queue = Queue() + assert len(queue) == 0 + del(queue) + + queue = Queue() + queue.enqueue(test_item) + assert len(queue) == 1 + del(queue) + + queue = Queue() + queue.enqueue(test_item) + queue.enqueue(test_item) + assert len(queue) == 2 + del(queue) + + queue = Queue() + queue.enqueue(test_item, test_item, test_item) + assert len(queue) == 3 + del(queue) + + def test_equal(self): + "Tests the overridden equal function." + stack1 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2 = Queue() + stack2.enqueue(test_item, test_item, test_item) + assert stack1 == stack2 + del(stack1) + del(stack2) + + def test_not_equal(self): + "Tests the overridden not equal function." + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item) + assert stack1 != stack2 + del(stack1) + del(stack2) + + def test_greater_than(self): + "Tests the overridden greater than function." + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item) + assert stack1 > stack2 + del(stack1) + del(stack2) + + def test_greater_equal(self): + "Tests the overridden greater or equal function." + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item) + assert stack1 >= stack2 + del(stack1) + del(stack2) + + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item, test_item) + assert stack1 >= stack2 + del(stack1) + del(stack2) + + def test_less_than(self): + "Tests the overridden less than function." + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item) + assert stack2 < stack1 + del(stack1) + del(stack2) + + def test_less_equal(self): + "Tests the overridden less or equal function." + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item) + assert stack2 <= stack1 + del(stack1) + del(stack2) + + stack1 = Queue() + stack2 = Queue() + stack1.enqueue(test_item, test_item, test_item) + stack2.enqueue(test_item, test_item, test_item) + assert stack1 <= stack2 + del(stack1) + del(stack2) + + def test_add(self): + "Tests the sum operator." + stack1 = Queue() + stack2 = Queue() + stack3 = Queue() + test_stack = Queue() + stack1.enqueue(test_item, test_item) + stack2.enqueue(test_item, test_item) + stack3.enqueue(test_item, test_item) + test_stack.enqueue(test_item, test_item, test_item, test_item, test_item, test_item) + assert stack1 + stack2 + stack3 == test_stack + del(stack1) + del(stack2) + del(stack3) + del(test_stack) + + +class TestRobustness(): + def test_push(self): + """ + Checks if pushing an + item to a queue works. + """ + queue = Queue() + queue.enqueue(test_item) + assert len(queue) == 1 + del(queue) + + queue = Queue() + queue.enqueue(test_item) + queue.enqueue(test_item) + assert len(queue) == 2 + del(queue) + + queue = Queue() + queue.enqueue(test_item, test_item, test_item) + assert len(queue) == 3 + del(queue) + + def test_pop(self): + """ + Checks if popping an + item from a queue works. + """ + queue = Queue() + queue.enqueue(test_item, test_item, test_item) + assert queue.dequeue() == test_item + assert len(queue) == 2 + del(queue) + + queue = Queue() + queue.enqueue(test_item) + assert queue.dequeue() == test_item + assert len(queue) == 0 + del(queue) + + def test_extend(self): + """ + Checks if the extend + method works correctly. + """ + stack1 = Queue() + stack2 = Queue() + test_stack = Queue() + stack1.enqueue(test_item, test_item) + stack2.enqueue(test_item, test_item) + test_stack.enqueue(test_item, test_item, test_item, test_item) + assert stack1.extend(stack2) == test_stack + diff --git a/test/datastructures/test_stack.py b/test/datastructures/test_stack.py index 131c65a..6ee197c 100644 --- a/test/datastructures/test_stack.py +++ b/test/datastructures/test_stack.py @@ -1,6 +1,5 @@ # jutils/test/datastructures/test_stack.py from jutl.datastructures import Stack -from time import sleep test_name = "Test name" test_item = "Item" @@ -21,7 +20,8 @@ def test_name(self): stack = Stack(test_name) assert stack.name == test_name del(stack) - + + class TestDunder(): def test_repr(self): "Tests what is output for representation." diff --git a/test/exceptions/test_emptyqueue.py b/test/exceptions/test_emptyqueue.py new file mode 100644 index 0000000..54858d5 --- /dev/null +++ b/test/exceptions/test_emptyqueue.py @@ -0,0 +1,12 @@ +#jutils/test/exceptions/test_emptyqueue.py +from jutl.exceptions import EmptyQueueError + +test_message = "Test message" +def func(): + raise EmptyQueueError(test_message) + +class TestException(): + def test_message(self): + "Tests if the exception message is correct." + exception = EmptyQueueError(test_message) + assert exception.message == test_message diff --git a/test/exceptions/test_emptystack.py b/test/exceptions/test_emptystack.py new file mode 100644 index 0000000..d02e27a --- /dev/null +++ b/test/exceptions/test_emptystack.py @@ -0,0 +1,12 @@ +#jutils/test/exceptions/test_emptyqueue.py +from jutl.exceptions import EmptyStackError + +test_message = "Test message" +def func(): + raise EmptyStackError(test_message) + +class TestException(): + def test_message(self): + "Tests if the exception message is correct." + exception = EmptyStackError(test_message) + assert exception.message == test_message diff --git a/test/exceptions/test_fullqueue.py b/test/exceptions/test_fullqueue.py new file mode 100644 index 0000000..dc58412 --- /dev/null +++ b/test/exceptions/test_fullqueue.py @@ -0,0 +1,12 @@ +#jutils/test/exceptions/test_emptyqueue.py +from jutl.exceptions import FullQueueError + +test_message = "Test message" +def func(): + raise FullQueueError(test_message) + +class TestException(): + def test_message(self): + "Tests if the exception message is correct." + exception = FullQueueError(test_message) + assert exception.message == test_message diff --git a/test/exceptions/test_fullstack.py b/test/exceptions/test_fullstack.py new file mode 100644 index 0000000..cfad59a --- /dev/null +++ b/test/exceptions/test_fullstack.py @@ -0,0 +1,12 @@ +#jutils/test/exceptions/test_emptyqueue.py +from jutl.exceptions import FullStackError + +test_message = "Test message" +def func(): + raise FullStackError(test_message) + +class TestException(): + def test_message(self): + "Tests if the exception message is correct." + exception = FullStackError(test_message) + assert exception.message == test_message