diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index bc858aa..f3cc687 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -14,4 +14,17 @@ jobs: with: python-version: "3.11" - name: flake8 Lint - uses: py-actions/flake8@v2 \ No newline at end of file + uses: py-actions/flake8@v2 + mypy: + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Checkout + uses: actions/checkout@v3 + - name: Install mypy + run: pip install mypy + - name: Run mypy + run: python -m mypy \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index d7c8d1a..ce25c78 100644 --- a/poetry.lock +++ b/poetry.lock @@ -47,6 +47,20 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "astroid" +version = "3.2.4" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + [[package]] name = "asyncio" version = "3.4.3" @@ -290,6 +304,21 @@ files = [ docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] tests = ["pytest", "pytest-cov", "pytest-xdist"] +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "dnspython" version = "2.6.1" @@ -703,6 +732,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -1010,6 +1053,17 @@ python-dateutil = ">=2.7" [package.extras] dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1264,6 +1318,22 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa typing = ["typing-extensions"] xmp = ["defusedxml"] +[[package]] +name = "platformdirs" +version = "4.3.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.1-py3-none-any.whl", hash = "sha256:facaa5a3c57aa1e053e3da7b49e0cc31fe0113ca42a4659d5c2e98e545624afe"}, + {file = "platformdirs-4.3.1.tar.gz", hash = "sha256:63b79589009fa8159973601dd4563143396b35c5f93a58b36f9049ff046949b1"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + [[package]] name = "pluggy" version = "1.5.0" @@ -1294,8 +1364,8 @@ files = [ annotated-types = ">=0.4.0" pydantic-core = "2.20.1" typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, ] [package.extras] @@ -1436,6 +1506,35 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pylint" +version = "3.2.7" +description = "python code static checker" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, + {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, +] + +[package.dependencies] +astroid = ">=3.2.4,<=3.3.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pyparsing" version = "3.1.4" @@ -1730,6 +1829,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + [[package]] name = "typer" version = "0.12.5" diff --git a/pyproject.toml b/pyproject.toml index 2ac29e6..a9eb6e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,14 @@ mysqlclient = "2.2.0" [tool.poetry.group.jupyter.dependencies] matplotlib = "^3.9.2" + +[tool.poetry.group.dev.dependencies] +mypy = "^1.11.2" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.mypy] +files = ['src', 'tests'] +ignore_missing_imports = true \ No newline at end of file diff --git a/src/algos/array/trapping_rain_water.py b/src/algos/array/trapping_rain_water.py index 2fdfce1..38745a8 100644 --- a/src/algos/array/trapping_rain_water.py +++ b/src/algos/array/trapping_rain_water.py @@ -2,34 +2,34 @@ def trap_rain_water(heights: list[int]) -> int: ans = 0 for i in range(1, len(heights)-1): - maxL, maxR = 0, 0 + max_l, max_r = 0, 0 for left in range(i, -1, -1): - maxL = max(maxL, heights[left]) + max_l = max(max_l, heights[left]) for right in range(i, len(heights)): - maxR = max(maxR, heights[right]) + max_r = max(max_r, heights[right]) - ans += min(maxL, maxR) - heights[i] + ans += min(max_l, max_r) - heights[i] return ans def trap_rain_water_dp(heights: list[int]) -> int: ans = 0 - leftMax = [0 for _ in range(len(heights))] - rightMax = [0 for _ in range(len(heights))] + left_max = [0 for _ in range(len(heights))] + right_max = [0 for _ in range(len(heights))] - leftMax[0] = heights[0] - rightMax[-1] = heights[-1] + left_max[0] = heights[0] + right_max[-1] = heights[-1] for i in range(1, len(heights)): - leftMax[i] = max(heights[i], leftMax[i - 1]) + left_max[i] = max(heights[i], left_max[i - 1]) for i in range(len(heights) - 2, 0, -1): - rightMax[i] = max(heights[i], rightMax[i+1]) + right_max[i] = max(heights[i], right_max[i+1]) for i in range(1, len(heights) - 1): - ans += min(leftMax[i], rightMax[i]) - heights[i] + ans += min(left_max[i], right_max[i]) - heights[i] return ans diff --git a/src/algos/array/two_pointers/squares_of_sorted_array.py b/src/algos/array/two_pointers/squares_of_sorted_array.py index 27d1cd7..7727b78 100644 --- a/src/algos/array/two_pointers/squares_of_sorted_array.py +++ b/src/algos/array/two_pointers/squares_of_sorted_array.py @@ -34,7 +34,7 @@ def return_squares(l1: list[int]) -> list[int]: ans[i] = negative_num ** 2 left -= 1 else: - if positive_num == 0 or (abs(positive_num) < abs(negative_num)): + if positive_num == 0 or int((abs(positive_num) < abs(negative_num))): ans[i] = positive_num ** 2 right += 1 else: diff --git a/src/algos/general/create_substeps.py b/src/algos/general/create_substeps.py index 1848b9b..f6adaf0 100644 --- a/src/algos/general/create_substeps.py +++ b/src/algos/general/create_substeps.py @@ -1,3 +1,6 @@ +"""_summary_""" + + def create_substeps(nums: list): index = 1 sub_steps = [] diff --git a/src/algos/general/decode.py b/src/algos/general/decode.py index f96fe5d..2c9bc05 100644 --- a/src/algos/general/decode.py +++ b/src/algos/general/decode.py @@ -1,6 +1,9 @@ +"""_summary_""" + + def decode(message_file: str): try: - with open(message_file, 'r') as file: + with open(message_file, 'r', encoding='utf-8') as file: text_lines = file.read().splitlines() except FileNotFoundError: print(f"file doesn't exist: {message_file}") diff --git a/src/algos/general/is_prime.py b/src/algos/general/is_prime.py index 2a910c3..22521f5 100644 --- a/src/algos/general/is_prime.py +++ b/src/algos/general/is_prime.py @@ -1,7 +1,17 @@ +"""_summary_ +""" from math import sqrt def is_prime(num: int) -> bool: + """_summary_ + + Args: + num (int): _description_ + + Returns: + bool: _description_ + """ if num > 1: for i in range(2, int(sqrt(num)) + 1): if num % i == 0: diff --git a/src/algos/trees/dfs/__init__.py b/src/algos/trees/dfs/__init__.py index 65f011d..7b205e4 100644 --- a/src/algos/trees/dfs/__init__.py +++ b/src/algos/trees/dfs/__init__.py @@ -1,2 +1,2 @@ from .reversal import reverse_tree, invert_tree -from .tree_depth import max_depth, min_depth +from .tree_depth import max_depth diff --git a/src/algos/trees/dfs/lca.py b/src/algos/trees/dfs/lca.py deleted file mode 100644 index d127c28..0000000 --- a/src/algos/trees/dfs/lca.py +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Optional -from src.data_structures.trees import TreeNode - - -def lowest_common_ancestor(root: Optional[TreeNode], a: int, b: int) -> Optional[TreeNode]: - return None diff --git a/src/algos/trees/dfs/sum.py b/src/algos/trees/dfs/sum.py index ae7613c..7499d84 100644 --- a/src/algos/trees/dfs/sum.py +++ b/src/algos/trees/dfs/sum.py @@ -24,6 +24,6 @@ def helper(root: Optional[TreeNode], cur_bits: list[int], ans: list[int]): rest = sum([2 ** (len(cur_bits) - i - 1) for i, bit in enumerate(cur_bits[:-1]) if bit == 1]) ans.append(rest + first_bit) - ans = [] + ans: list[int] = [] helper(root, [], ans) return sum(ans) diff --git a/src/algos/trees/dfs/tree_depth.py b/src/algos/trees/dfs/tree_depth.py index 650e47f..d0152cd 100644 --- a/src/algos/trees/dfs/tree_depth.py +++ b/src/algos/trees/dfs/tree_depth.py @@ -9,7 +9,3 @@ def max_depth(root: Optional[TreeNode]) -> int: left_max = max_depth(root.left) right_max = max_depth(root.right) return max(left_max, right_max) + 1 - - -def min_depth(root: Optional[TreeNode]) -> int: - ... diff --git a/src/algos/trees/traversal/iterative_traversal.py b/src/algos/trees/traversal/iterative_traversal.py index 2a76188..d3707a5 100644 --- a/src/algos/trees/traversal/iterative_traversal.py +++ b/src/algos/trees/traversal/iterative_traversal.py @@ -6,7 +6,7 @@ def iterative_inorder_traversal(root: Optional[TreeNode], ans: list[int]): cur = root stack = [] - while len(stack) or cur: + while len(stack) > 0 or cur: while cur: stack.append(cur) cur = cur.left diff --git a/src/algos/trees/traversal/recursive_traversal.py b/src/algos/trees/traversal/recursive_traversal.py index 49b8dc8..37d6f6c 100644 --- a/src/algos/trees/traversal/recursive_traversal.py +++ b/src/algos/trees/traversal/recursive_traversal.py @@ -7,8 +7,8 @@ def recursive_preorder_traversal(root: Optional[TreeNode], nums: list[int]): return nums.append(root.val) - recursive_preorder_traversal(root.left) - recursive_preorder_traversal(root.right) + recursive_preorder_traversal(root.left, nums) + recursive_preorder_traversal(root.right, nums) def recursive_inorder_traversal(root: Optional[TreeNode], nums: list[int]): diff --git a/src/data_structures/__pycache__/linked_list.cpython-310.pyc b/src/data_structures/__pycache__/linked_list.cpython-310.pyc index 63aff48..5448431 100644 Binary files a/src/data_structures/__pycache__/linked_list.cpython-310.pyc and b/src/data_structures/__pycache__/linked_list.cpython-310.pyc differ diff --git a/src/data_structures/graphs/disjoint_set.py b/src/data_structures/graphs/disjoint_set.py index dcb51a8..376e0dc 100644 --- a/src/data_structures/graphs/disjoint_set.py +++ b/src/data_structures/graphs/disjoint_set.py @@ -7,12 +7,12 @@ def __find(self, x): return self._root[x] def union(self, x: int, y: int): - rootX = self.__find(x) - rootY = self.__find(y) + root_x = self.__find(x) + root_y = self.__find(y) for i in range(self.size): - if self._root[i] == rootY: - self._root[i] = rootX + if self._root[i] == root_y: + self._root[i] = root_x def connected(self, x: int, y: int) -> bool: return self.__find(x) == self.__find(y) @@ -33,11 +33,11 @@ def __find(self, x): return x def union(self, x: int, y: int): - rootX = self.__find(x) - rootY = self.__find(y) + root_x = self.__find(x) + root_y = self.__find(y) - if rootX != rootY: - self._root[rootY] = rootX + if root_x != root_y: + self._root[root_y] = root_x def connected(self, x: int, y: int) -> bool: return self.__find(x) == self.__find(y) diff --git a/src/data_structures/linked_list.py b/src/data_structures/linked_list.py index 5a24b85..d138eb8 100644 --- a/src/data_structures/linked_list.py +++ b/src/data_structures/linked_list.py @@ -1,8 +1,8 @@ class LinkedList: - def __init__(self, val: int, next=None): + def __init__(self, val: int, next_list=None): self._val = val - self.next = next + self.next = next_list @property def val(self): diff --git a/src/domain_driven_design/cosmic_python/README.md b/src/domain_driven_design/cosmic_python/README.md new file mode 100644 index 0000000..27049e8 --- /dev/null +++ b/src/domain_driven_design/cosmic_python/README.md @@ -0,0 +1 @@ +https://www.cosmicpython.com/book/chapter_01_domain_model.html \ No newline at end of file diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/__init__.py b/src/domain_driven_design/cosmic_python/domain_modeling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/domain_driven_design/cosmic_python/domain_modeling/model.py b/src/domain_driven_design/cosmic_python/domain_modeling/model.py new file mode 100644 index 0000000..88ad853 --- /dev/null +++ b/src/domain_driven_design/cosmic_python/domain_modeling/model.py @@ -0,0 +1,55 @@ +from typing import TypeVar +from dataclasses import dataclass +from typing import Optional +from datetime import date + + +SKU = TypeVar("SKU", bound=str) + + +@dataclass(frozen=True) +class OrderLine: + order_id: str + sku: SKU + qty: int + + def __eq__(self, other): + return self.order_id == other.order_id + + +class Batch: + def __init__(self, + ref: str, + sku: SKU, + qty: int, + eta: Optional[date]): + self.reference = ref + self.sku = sku + self.eta = eta + self._purchased_quantity = qty + self._allocations: set[OrderLine] = set() + + def allocate(self, line: OrderLine) -> bool: + if self.can_allocate(line): + self._allocations.add(line) + return True + else: + return False + + def deallocate(self, line: OrderLine) -> bool: + if line in self._allocations: + self._allocations.remove(line) + return True + else: + return False + + @property + def allocated_quantity(self) -> int: + return sum(line.qty for line in self._allocations) + + @property + def available_quantity(self) -> int: + return self._purchased_quantity - self.allocated_quantity + + def can_allocate(self, line: OrderLine) -> bool: + return self.sku == line.sku and self.available_quantity >= line.qty diff --git a/src/general_python/threading/asyncio_queues/__init__.py b/src/general_python/threading/asyncio_queues/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/general_python/threading/consumer_producer_threading/__init__.py b/src/general_python/threading/consumer_producer_threading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/general_python/threading/consumer_producer_threading/consumer.py b/src/general_python/threading/consumer_producer_threading/consumer.py deleted file mode 100644 index 04cf1b8..0000000 --- a/src/general_python/threading/consumer_producer_threading/consumer.py +++ /dev/null @@ -1,16 +0,0 @@ -import logging -from settings import get_app_settings - - -def consumer(pipeline, event): - """Pretend we're saving a number in the database.""" - logger = logging.getLogger(get_app_settings().logger_name) - while not event.is_set() or not pipeline.empty(): - message = pipeline.get_message("Consumer") - logger.info( - "Consumer storing message: %s (queue size=%s)", - message, - pipeline.qsize(), - ) - - logger.info("Consumer received EXIT event. Exiting") diff --git a/src/general_python/threading/consumer_producer_threading/main.py b/src/general_python/threading/consumer_producer_threading/main.py index 7b44f51..f8bb47c 100644 --- a/src/general_python/threading/consumer_producer_threading/main.py +++ b/src/general_python/threading/consumer_producer_threading/main.py @@ -2,13 +2,37 @@ import threading import time from concurrent import futures +import random from logging_config import setup_custom_logger from settings import get_app_settings -from consumer import consumer -from producer import producer from pipeline import Pipeline +def consumer(pipeline, event): + """Pretend we're saving a number in the database.""" + logger = logging.getLogger(get_app_settings().logger_name) + while not event.is_set() or not pipeline.empty(): + message = pipeline.get_message("Consumer") + logger.info( + "Consumer storing message: %s (queue size=%s)", + message, + pipeline.qsize(), + ) + + logger.info("Consumer received EXIT event. Exiting") + + +def producer(pipeline, event): + """Pretend we're getting a number from the network.""" + logger = logging.getLogger(get_app_settings().logger_name) + while not event.is_set(): + message = random.randint(1, 101) + logger.info("Producer got message: %s", message) + pipeline.set_message(message, "Producer") + + logger.info("Producer received EXIT event. Exiting") + + if __name__ == "__main__": setup_custom_logger(get_app_settings().logger_name) logger = logging.getLogger(get_app_settings().logger_name) diff --git a/src/general_python/threading/consumer_producer_threading/producer.py b/src/general_python/threading/consumer_producer_threading/producer.py deleted file mode 100644 index 0e8ee3a..0000000 --- a/src/general_python/threading/consumer_producer_threading/producer.py +++ /dev/null @@ -1,14 +0,0 @@ -import random -import logging -from settings import get_app_settings - - -def producer(pipeline, event): - """Pretend we're getting a number from the network.""" - logger = logging.getLogger(get_app_settings().logger_name) - while not event.is_set(): - message = random.randint(1, 101) - logger.info("Producer got message: %s", message) - pipeline.set_message(message, "Producer") - - logger.info("Producer received EXIT event. Exiting") diff --git a/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py b/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py index 3feb7b7..560f1c9 100644 --- a/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py +++ b/src/system_design/rest/fastapi_crud/app/routers/fake_recipes_db.py @@ -1,5 +1,5 @@ from typing import Optional -from enum import Enum +from enum import IntEnum from ..model import RecipeModel @@ -21,7 +21,7 @@ ] -class RecipeSearchType(Enum): +class RecipeSearchType(IntEnum): id = 0 name = 1 diff --git a/tests/domain_driven_design/domain_modeling/test_batches.py b/tests/domain_driven_design/domain_modeling/test_batches.py new file mode 100644 index 0000000..a06d886 --- /dev/null +++ b/tests/domain_driven_design/domain_modeling/test_batches.py @@ -0,0 +1,43 @@ +from datetime import date +from src.domain_driven_design.cosmic_python.domain_modeling.model import Batch, OrderLine + + +def make_batch_and_line(sku, batch_qty, line_qty): + return ( + Batch("batch-001", sku, batch_qty, eta=date.today()), + OrderLine("order-123", sku, line_qty) + ) + + +def test_can_allocate_if_available_greater_than_required(): + large_batch, small_line = make_batch_and_line("ELEGANT-LAMP", 20, 2) + assert large_batch.can_allocate(small_line) + assert large_batch.available_quantity == 20 + assert large_batch.allocate(small_line) + assert large_batch.available_quantity == 18 + + +def test_cannot_allocate_if_available_smaller_than_required(): + small_batch, large_line = make_batch_and_line("ELEGANT-LAMP", 2, 20) + assert not small_batch.can_allocate(large_line) + + +def test_can_allocate_if_available_equal_to_required(): + batch, line = make_batch_and_line("ELEGANT-LAMP", 2, 2) + assert batch.can_allocate(line) + assert batch.allocate(line) + assert not batch.can_allocate(line) + + +def test_cannot_allocate_if_skus_do_not_match(): + batch = Batch("batch-001", "UNCOMFORTABLE-CHAIR", 100, eta=None) + different_sku_line = OrderLine("order-123", "EXPENSIVE-TOASTER", 10) + assert not batch.can_allocate(different_sku_line) + + +def test_can_only_deallocated_allocated_lines(): + batch, unallocated_line = make_batch_and_line("DECORATIVE-TRINKET", 20, 2) + assert not batch.deallocate(unallocated_line) + batch.allocate(unallocated_line) + assert batch.available_quantity == 18 + assert batch.deallocate(unallocated_line) diff --git a/tests/domain_driven_design/domain_modeling/test_model.py b/tests/domain_driven_design/domain_modeling/test_model.py new file mode 100644 index 0000000..fac3833 --- /dev/null +++ b/tests/domain_driven_design/domain_modeling/test_model.py @@ -0,0 +1,10 @@ +from src.domain_driven_design.cosmic_python.domain_modeling.model import OrderLine + + +def test_order_line_comparison(): + order_line_1 = OrderLine("ORD0001", "T-SHIRT", 100) + order_line_2 = OrderLine("ORD0001", "T-SHIRT", 200) + order_line_3 = OrderLine("ORD0002", "T-SHIRT", 100) + assert order_line_1 == order_line_2 + assert order_line_2 != order_line_3 + assert order_line_1 != order_line_3